LGP6623 [PUTS 2020A] 树 学习笔记
蓝的写。先放个带注释代码凑数。
```cpp
#include <bits/stdc++.h>
using namespace std;
namespace obasic{
typedef long long lolo;
typedef vector<int> vecint;
template <typename _T>
void readi(_T &x){
_T k=1;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
x*=k;return;
}
template <typename _T>
void writi(_T x){
if(x<0)putchar('-'),x=-x;
if(x>9)writi(x/10);
putchar(x%10+'0');
}
};
using namespace obasic;
const int MaxN=5.3e5,MaxNb=21;
int N,A[MaxN],X,W[MaxNb][MaxN];
vector<int> Tr[MaxN];lolo fans;
void addedge(int u,int v){Tr[u].push_back(v);}
lolo dfs(int u,int d){
int cans=A[u];
for(int i=0;i<MaxNb;i++)W[i][(d+A[u])&((1<<i)-1)]^=(1<<i);
//我们来思考一下,首先我们知道当我们按位考虑
//第k位会给自己的U[b2^(k+1)+a,b2^(k+1)+a+2^k)级祖先打一个加1的贡献。
//要给这么多区间打贡献,不如直接打差分。然而这样仍然是O(N^2)的。
//观察到所有要打上差分的下标模2^k相等,所以把它们丢在一个桶里面自取。
//问题来了,这个a等价于什么?代码里面又要怎么写?
//(上文a并不实际出现在代码中)
//推理一下,深度为d的u,其末k位为A[u]的;
//深度为d-1的u,其末那些位就是为A[u]+1的。
//(感性理解,模意义下)深度为d+A[u]的u,其末那些位就是为0的。
//相应地深度为d+A[u]+2^k的u开始其末那些位就是为1的。
//那差分数组就刚好打在所有模2^k余((d+A[u])%(2^k))的地方了
//很巧妙的树上开桶差分算贡献,可以和LGP1600相媲美
//建议对照学习,更深刻地体会树上差分思想。
for(int i=0;i<MaxNb;i++)cans^=W[i][d&((1<<i)-1)];
for(int v : Tr[u])cans^=dfs(v,d+1);
for(int i=0;i<MaxNb;i++)cans^=W[i][d&((1<<i)-1)];
//进dfs异或一道,出dfs再异或一道
//可以理解为先xor a再xor (a xor b)
//等效于xor b,b即子树内做出来的贡献
//这归功于异或有自反性。
fans+=cans;return cans;
}
int main(){
readi(N);
for(int i=1;i<=N;i++)readi(A[i]);
for(int i=2;i<=N;i++)readi(X),addedge(X,i);
dfs(1,0);writi(fans);
return 0;
}