【洛谷6623】[省選聯考 2020 A 卷] 樹(有趣的Trie樹題)
阿新 • • 發佈:2020-07-16
大致題意: 給定一棵樹,節點\(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; }