Luogu P6623 [省选联考 2020 A 卷] 树|01Trie
题目大意:
[1leq n,v_i leq 525010,1leq p_ileq n
]
思路:
这题有写树上差分的,桶+Dsu on tree的,这里来一种01trie的方法。
考虑使用dfs,在每个点维护一个trie,对于每个节点,先与子节点的trie合并,然后按位统计该位(0/1)个数,并在向上更新答案时进行(+1)的操作,得出答案。
二进制中的加(1),就是从末位开始,若该位为(1),则变(0),同时往下一为加(1),直到该位为(0),则变(1),因此在树中不断交换(0)子树和(1)子树,并向(0)子树递归即可。显而易见的,建树要从最低位开始建。
01trie中的合并与线段树合并相差不大,稍作修改即可。
上代码:
#include<bits/stdc++.h>
using namespace std;
int w[530000][22][2],g,tmp,rot[530000];
//在实现中,用w数组存每个节点每位的0/1个数
int cc,to[530000],net[530000],fr[530000],v[530000],n,fa;
long long ans;
struct trie
{
int f0,f1,s;
}t[30052210];//需要空间较大,务必注意
void addedge(int u,int v)
{
cc++;
to[cc]=v;net[cc]=fr[u];fr[u]=cc;
}
void add(int x,int y,int v)
{
t[x].s++;
if (y==21) return ;
if (v&1)
{
if (!t[x].f1) g++;t[x].f1=g;
add(t[x].f1,y+1,v>>1);w[tmp][y+1][1]++;
}
else
{
if (!t[x].f0) g++;t[x].f0=g;
add(t[x].f0,y+1,v>>1);w[tmp][y+1][0]++;
}
}
void hebing(int x,int y)
{
t[x].s+=t[y].s;
if ((!x)||(!y)) return;
if (t[y].f0&&!t[x].f0)
t[x].f0=t[y].f0;
else hebing(t[x].f0,t[y].f0);
if (t[y].f1&&!t[x].f1)
t[x].f1=t[y].f1;
else hebing(t[x].f1,t[y].f1);
} //01trie合并
void plusone(int x,int y)
{
if (!x) return ;
w[tmp][y+1][0]-=t[t[x].f0].s;
w[tmp][y+1][1]-=t[t[x].f1].s;
swap(t[x].f0,t[x].f1);
w[tmp][y+1][0]+=t[t[x].f0].s;
w[tmp][y+1][1]+=t[t[x].f1].s;
plusone(t[x].f0,y+1);
}//加一
void dfs(int x)
{
int s=0;
rot[x]=++g;
tmp=x;add(rot[x],0,v[x]);
for (int i=fr[x];i;i=net[i])
{
dfs(to[i]);
hebing(rot[x],rot[to[i]]);
for (int j=1;j<=21;j++)
{
w[x][j][0]+=w[to[i]][j][0];
w[x][j][1]+=w[to[i]][j][1];
}
}
for (int i=1;i<=21;i++)
{
if (w[x][i][1]%2) s+=1<<(i-1);
}
ans+=s;
tmp=x;plusone(rot[x],0);
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
for (int i=2;i<=n;i++)
{
scanf("%d",&fa);
addedge(fa,i);
}
dfs(1);
cout<<ans<<endl;
return 0;
}