1. 程式人生 > 實用技巧 >【洛谷6623】[省選聯考 2020 A 卷] 樹(有趣的Trie樹題)

【洛谷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;
}