1. 程式人生 > 實用技巧 >【題解】 「聯合省選2020」樹 trie樹合併 LOJ3303

【題解】 「聯合省選2020」樹 trie樹合併 LOJ3303

Legend

Link \(\textrm{to LOJ}\)

給定 \(n\ (1 \le n \le 525010)\) 個節點的有根樹,根為 \(1\),邊權為 \(1\),每個點有權值 \(v_i\ (1 \le v_i \le 525010)\)

定義一個節點 \(x\) 對答案的貢獻為:

\[c_i = \bigoplus \limits_{y \in \rm{subtree}} v_{y}+dist(x,y) \]

請求出 \(\sum\limits_{i=1}^n c_i\)

Editorial

簡單來說,有這麼一個想法:實現一個數據結構支援

  • 合併兩個集合;
  • 集合所有數字 \(+1\)
  • 求集合所有元素異或和。

樹上的合併讓人很容易想到線段樹合併,但是線段樹合併做不到讓所有數字 \(+1\)

其實你要這麼想:為什麼是 \(+1\) 而不是加任意權值?是不是因為 \(\rm{std}\) 做不了?

眾所周知如果你看了這篇部落格,那你應該很快意識到一個問題,\(+1\) 只導致 \(\log v\) 次進位。

所以我們直接從低位到高位建立 \(\textrm{trie}\) 樹,然後 \(\textrm{trie}\) 樹合併即可。

複雜度 \(O(n \log v)\)

Code

這是個 \(\textrm{trie}\) 樹,但我們也不妨把它建成一棵線段樹。

這題成功卡了我指標線段樹的空間,所以只能嘗試寫陣列版本啦。

#include <bits/stdc++.h>

#define LL long long

#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9')
		x = x * 10 + k - '0' ,k = getchar();
	return x;
}

const int MX = 525010 + 233;
int v[MX];

int head[MX] ,tot;
struct edge{
	int node ,next;
}h[MX << 1];
void addedge(int u ,int v){
	h[++tot] = (edge){v ,head[u]} ,head[u] = tot;
}

LL Ans;

int cnt;
struct node{
	int l ,r ,size ,__xor ,ans;
	int ch[2];
	#define lc tr[ch[0]]
	#define rc tr[ch[1]]
	void pushup();
	void doxor(int v);
	void pushdown();
	void insert(int v ,int p);
	void doit(int dep);
	// #undef lc
	// #undef rc
}tr[MX * 64];

void node::pushup(){
	size = 0;
	if(ch[0]) size += lc.size;
	if(ch[1]) size += rc.size;
	ans = 0;
	if(ch[0]) ans ^= lc.ans;
	if(ch[1]) ans ^= rc.ans;
}

void node::doxor(int v){
	ans ^= (size & 1) * v;
	__xor ^= v;
}

void node::pushdown(void){
	if(__xor){
		if(ch[0]) lc.doxor(__xor);
		if(ch[1]) rc.doxor(__xor);
		__xor = 0;
	}
}

int newnode(int l ,int r){
	node *x = &tr[++cnt];
	x->l = l ,x->r = r ,x->size = x->__xor = x->ans = 0;
	x->ch[0] = x->ch[1] = 0;
	return cnt;
}

int merge(int x ,int y){
	if(!x) return y;
	if(!y) return x;
	if(tr[x].l == tr[x].r){
		tr[x].size += tr[y].size;
		tr[x].ans ^= tr[y].ans;
	}
	else{
		tr[x].pushdown() ,tr[y].pushdown();
		tr[x].ch[0] = merge(tr[x].ch[0] ,tr[y].ch[0]);
		tr[x].ch[1] = merge(tr[x].ch[1] ,tr[y].ch[1]);
		tr[x].pushup();
	}
	return x;
}

void node::insert(int v ,int p = 0){
	if(l == r){
		size += 1;
		ans ^= v;
		return ;
	}
	pushdown();
	int mid = (l + r) >> 1;
	int where = (v >> p) & 1;
	if(ch[where] == 0){
		if(where == 0) ch[0] = newnode(l ,mid);
		else ch[1] = newnode(mid + 1 ,r);
	}
	tr[ch[where]].insert(v ,p + 1);
	pushup();
}

void node::doit(int dep = 0){
	if(l == r) return;
	pushdown();
	std::swap(ch[0] ,ch[1]);
	if(ch[0]){
		lc.doxor(1 << dep);
		lc.doit(dep + 1);
	}
	if(ch[1]){
		rc.doxor(1 << dep);
	}
	pushup();
}

void DFS(int x){
	tr[x].insert(v[x]);
	for(int i = head[x] ,d ; i ; i = h[i].next){
		DFS(d = h[i].node);
		merge(x ,d);
	}
	// tr[x].insert(v[x]);
	Ans += tr[x].ans;
	// debug("Ans[%d] = %d\n" ,x ,tr[x].ans);
	tr[x].doit();
}

int main(){
	__FILE([省選聯考2020]樹);
	int n = read();
	for(int i = 1 ; i <= n ; ++i){
		v[i] = read();
		newnode(0 ,(1 << 21) - 1);
	}
	for(int i = 2 ; i <= n ; ++i) addedge(read() ,i);
	DFS(1);
	std::cout << Ans << std::endl;
	return 0;
}