正题
题目链接:https://www.luogu.com.cn/problem/P6623
题目大意
一棵树,每个节点有一个权值valival_ivali,定义disi,jdis_{i,j}disi,j表示iii到jjj的距离。
一个节点xxx的权值定义为该节点子树中的每个节点yyy的disx,y+valjdis_{x,y}+val_{j}disx,y+valj的异或和。
求所有节点的权值和
解题思路
对于一个二进制010101串,我们可以用TrieTrieTrie从高位到低位存,我们记录TrieTrieTrie上每个位置的答案,考虑如何让整个TrieTrieTrie上的数字+1+1+1。
显然对于一个000指向的节点,它会变成111,对于111指向的节点,它会变成000并且进位。也就是我们交换左右子树后再向原来111指向的节点进位。
这样我们就实现了一个可以插入数字或者全部+1+1+1来维护答案的数据结构,之后用树上启发式合并即可。
时间复杂度O(nlog2n)O(n\log^2 n)O(nlog2n)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6e5+10,M=27;
struct node{int to,next;
}a[N];
int n,tot,cnt,ls[N],siz[N],son[N];
int w[N],s[N*M],v[N*M],ch[N*M][2],ed[N*M],z;
long long ans;
void addl(int x,int y){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;
}
void Make(int &x,int k){if(!x){x=++cnt;ch[x][0]=ch[x][1]=s[x]=ed[x]=v[x]=0;}if(!k){s[x]^=1;ed[x]^=1;return;}Make(ch[x][k&1],k>>1);s[x]=s[ch[x][0]]^s[ch[x][1]]^ed[x];v[x]=(v[ch[x][0]]<<1)^((v[ch[x][1]]<<1)|s[ch[x][1]]);return;
}
void Merge(int x){if(!x){return;}swap(ch[x][0],ch[x][1]);if(ed[x]&&!ch[x][1]){ch[x][1]=++cnt;ch[cnt][0]=ch[cnt][1]=s[cnt]=ed[cnt]=v[cnt]=0;}ed[ch[x][1]]^=ed[x];s[ch[x][1]]^=ed[x];ed[x]=0;Merge(ch[x][0]);s[x]=s[ch[x][0]]^s[ch[x][1]]^ed[x];v[x]=(v[ch[x][0]]<<1)^((v[ch[x][1]]<<1)|s[ch[x][1]]);return;
}
void dfs(int x){siz[x]=1;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;dfs(y);siz[x]+=siz[y];if(siz[y]>siz[son[x]])son[x]=y;}return;
}
void calc(int x,int dep){Make(z,w[x]+dep);for(int i=ls[x];i;i=a[i].next){int y=a[i].to;calc(y,dep+1);}return;
}
void solve(int x){for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==son[x])continue;solve(y);}cnt=z=0;if(son[x])solve(son[x]),Merge(z);for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==son[x])continue;calc(y,1);}Make(z,w[x]);ans+=v[1];return;
}
int main()
{freopen("tree2.in","r",stdin);scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&w[i]);for(int i=2;i<=n;i++){int x;scanf("%d",&x);addl(x,i);}dfs(1);solve(1);printf("%lld",ans);
}