loj3303.「联合省选 2020 A」树
首先有自然的思路就是 dfs 一遍整棵树,然后把儿子的信息合并起来加上自己的求一个答案。
求异或和,想到 01-trie。这一类数据结构合并都很没有营养,模仿线段树合并的过程直接合并就行,麻烦在于全局 +1 的操作。
考虑一种特殊的 01-trie。我们不是按照求异或最大值问题的模型,从高位到低位建树,因为这样进位无法处理,我们从低位到高位建树,这样进位只要递归就能继续处理。
考虑 +1 时,如果走到一个节点,那么 \(0+1\) 变成 \(1\),\(1+1\) 进位之后变成了 \(0\),相当于交换了左右儿子,然后还需要递归原来是 \(1\) 的那一边处理进位。但是我们需要自下而上更新异或和,维护方法是左右儿子异或和异或起来,再异或上右儿子那边的数字个数的奇偶性(即 \(1\) 的个数的奇偶性)。所以需要先递归 \(1\) 的子树再交换儿子以保持答案正确性。
这样就很简单了,dfs 一遍树,到每个点先合并所有儿子,然后全局 +1,最后插入自己的权值,然后算贡献即可。
#include<iostream>
#include<cstdio>
using namespace std;
#define int long long
struct edge
{
int nxt,to;
}e[600001];
int n,tot,h[600001],v[600001],cnt,ch[600001*71][2],val[600001*71],ans;
bool s[600001*71];
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x;
}
inline void add(int x,int y)
{
e[++tot].nxt=h[x];
h[x]=tot;
e[tot].to=y;
}
inline void push_up(int k,int d)
{
val[k]=val[ch[k][0]]^val[ch[k][1]]^(s[ch[k][1]]<<d);
}
inline int merge(int x,int y,int d)
{
if(ch[y][0])
{
if(ch[x][0])
ch[x][0]=merge(ch[x][0],ch[y][0],d+1);
else
ch[x][0]=ch[y][0];
}
if(ch[y][1])
{
if(ch[x][1])
ch[x][1]=merge(ch[x][1],ch[y][1],d+1);
else
ch[x][1]=ch[y][1];
}
s[x]^=s[y];
push_up(x,d);
return x;
}
void insert(int x,int node,int d)
{
if(d>23)
return;
s[node]^=1;
if(!ch[node][x&1])
ch[node][x&1]=++cnt;
insert(x>>1,ch[node][x&1],d+1);
push_up(node,d);
}
void update(int node,int d)
{
if(!node||d>23)
return;
update(ch[node][1],d+1);
ch[node][0]^=ch[node][1]^=ch[node][0]^=ch[node][1];
push_up(node,d);
}
void dfs(int k)
{
for(register int i=h[k];i;i=e[i].nxt)
{
dfs(e[i].to);
merge(k,e[i].to,0);
}
update(k,0);
insert(v[k],k,0);
ans+=val[k];
}
signed main()
{
cnt=n=read();
for(register int i=1;i<=n;++i)
v[i]=read();
for(register int i=2;i<=n;++i)
{
int f=read();
add(f,i);
}
dfs(1);
printf("%lld\n",ans);
return 0;
}