【洛谷6623】[省选联考 2020 A 卷] 树(有趣的Trie树题)
大致题意: 给定一棵树,节点\(i\)点权为\(v_i\)。定义\(val(x)=xor_y(v_y+d(x,y))\),其中\(y\)为\(x\)子树内的点(包括\(x\)自身),\(d(x,y)\)为\(x,y\)的树上距离,求\(\sum_{i=1}^nval(i)\)。
前言
\(Trie\)树的这种套路之前接触过,因此很快就做了出来。
由于这是一道挺有趣的题,于是就写了写。
题意转化
由于\(val(x)\)的求解对象是子树内的点,容易想到先求出子树内的答案,然后通过数据结构合并得到父节点的答案。
考虑子节点的答案到了父节点,所有距离加\(1\),同时还要统计上父节点自身的贡献。
也就是要支持下列几项操作:增加一个数、合并、求总异或和、给所有数加\(1\)。
看到异或想到\(Trie\)树,且前三项操作显然都很容易就可以用\(Trie\)树维护。
而最后的给所有数加\(1\),其实有一个套路做法。
给所有数加\(1\)
与一般\(Trie\)树相反,考虑我们从低位到高位建一棵\(Trie\)树。
每次加\(1\),原先的\(0\)变成了\(1\),原先的\(1\)变成了\(0\),然后还要进位加\(1\)。
其实也就是,交换左右儿子,然后递归继续给新的左儿子加\(1\)。
于是这道题就做完了,具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 525010
using namespace std;
int n,a[N+5],f[N+5],Rt[N+5],ans[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
class Trie
{
private:
#define LN 25
int Nt;struct node {int V,S[2];}O[N*LN<<1];
public:
I void Ins(int& rt,CI x,CI d=0)//增加一个数
{
if(d==LN) return;!rt&&(rt=++Nt),O[rt].V^=1,Ins(O[rt].S[x&1],x>>1,d+1);
}
I void U(CI rt,int& res,CI d=0)//给所有数加1
{
if(!rt) return;res^=(O[O[rt].S[0]].V^O[O[rt].S[1]].V)<<d,//更新这一位的答案
swap(O[rt].S[0],O[rt].S[1]),U(O[rt].S[0],res,d+1);//交换左右儿子,接着递归处理进位
}
I void Merge(int& x,CI y)//Trie树合并
{
if(!x||!y) return (void)(x|=y);O[x].V^=O[y].V,
Merge(O[x].S[0],O[y].S[0]),Merge(O[x].S[1],O[y].S[1]);
}
}T;
int main()
{
RI i;for(F.read(n),i=1;i<=n;++i) F.read(a[i]);for(i=2;i<=n;++i) F.read(f[i]);
long long t=0;for(i=n;i;--i) T.U(Rt[i],ans[i]),T.Ins(Rt[i],a[i]),//子树内所有点距离加1,然后统计当前点贡献
ans[i]^=a[i],t+=ans[i],i^1&&(T.Merge(Rt[f[i]],Rt[i]),ans[f[i]]^=ans[i]);//计算答案,然后把Trie树合并给父节点
return printf("%lld\n",t),0;
}
待到再迷茫时回头望,所有脚印会发出光芒