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;
}
posted @ 2025-02-15 08:17  矞龙OrinLoong  阅读(2)  评论(0编辑  收藏  举报