1. 程式人生 > 其它 >2020CCPC長春 F. Strange Memory(樹上啟發式合併)

2020CCPC長春 F. Strange Memory(樹上啟發式合併)

Once there was a rooted tree. The tree contained n nodes, which were numbered 1,…,n. The node numbered 1 was the root of the tree. Besides, every node i was assigned a number a**i. Your were surprised to find that there were several pairs of nodes (i,j) satisfying a**ia**j=alca(i,j),

where ⊕ denotes the bitwise XOR operation, and lca(i

,j) is the lowest common ancestor of i and j, or formally, the lowest (i.e. deepest) node that has both i and j as descendants.

Unfortunately, you cannot remember all such pairs, and only remember the sum of ij for all different pairs of nodes (i,j) satisfying the above property. Note that (i,j) and (j

,i) are considered the same here. In other words, you will only be able to recall ni=1nj=i+1a**ia**j=alca(i,j).

You are assumed to calculate it now in order to memorize it better in the future.

首先暴力做法就是雙重遍歷點求LCA然後判斷,複雜度顯然爆炸,考慮如何優化。既然列舉點不可,那麼可以考慮列舉LCA,求LCA為根的子樹對於答案的貢獻,加起來就是最終的答案了。這樣就自然想到了dsu on tree。由於異或的性質,可知要滿足的條件\(a[x]\ xor\ a[y] = a[LCA]\)

等價於\(a[y]=a[LCA]\ xor\ a[x]\),那麼可以維護一個vector陣列vector<int> vec[N],vec[i]儲存值為i的點的下標。那麼在遍歷LCA的時候先暴力搜輕兒子,再搜重兒子且保留資訊(存在vec陣列),再依次搜輕兒子,對於每個輕兒子為根的子樹先更新答案再更新vec陣列(這樣保證產生貢獻的兩個點的最近公共祖先一定是列舉到的LCA)。但這樣的話每次需要掃一遍vector,複雜度也是無法接受的。注意到異或運算的話每一位都是獨立的,可以考慮分別計算每一位的貢獻然後加起來。設cnt[i, j, k]表示值為i,下標第j位為k的數的個數。只有兩個下標第j位不相同,這一位才對答案有貢獻,因此獲取k的時候需要用x的第j位和1異或。

#include <iostream>
#include <cstring>
#define ll long long
const int N = 1e5 + 5;
using namespace std;
int n, a[N], head[N], ver[2 * N], Next[2 * N], tot = 0, sz[N], son[N];
long long ans = 0;
int hson = 0;
int cnt[N * 15][21][2];//第一維一定要開夠
void add(int x, int y) {
	ver[++tot] = y, Next[tot] = head[x], head[x] = tot;
}
void dfs1(int x, int pre) {
	sz[x] = 1;
	int mxsz = -1;
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == pre) continue;
		dfs1(y, x);
		sz[x] += sz[y];
		if(sz[y] > mxsz) {
			son[x] = y;
			mxsz = sz[y];
		}
	}
}
void update(int x, int pre, int lca) {
	int tmp = a[x] ^ a[lca];
	for(int i = 0; i <= 20; i++) {
		ans += (1ll << i) * (cnt[tmp][i][!((x >> i) & 1)]);
	}
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == pre || y == hson) continue;
		update(y, x, lca);
	}
}
void modify(int x, int pre, int v) {
	for(int i = 0; i <= 20; i++) {
		cnt[a[x]][i][(x >> i) & 1] += v;//x的資訊必須在這裡新增才不會產生影響
	}
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == pre || y == hson) continue;
		modify(y, x, v);
	}
}
void dfs2(int x, int pre, bool keep) {
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == pre || y == son[x]) continue;
		dfs2(y, x, 0);
	}
	if(son[x]) {
		dfs2(son[x], x, 1);
		hson = son[x];
	}
	for(int i = head[x]; i; i = Next[i]) {//統計以a[x]為lca的答案
		int y = ver[i];
		if(y == pre || y == son[x]) continue;//因為重兒子已經add過一遍了,不能再添加了
		//必須先計算再新增 否則可能出現v在u到假設的lca的鏈上,這樣假設的lca就不是真正的lca了
		update(y, x, x);
		modify(y, x, 1);
	}
	for(int i = 0; i <= 20; i++) {
		cnt[a[x]][i][(x >> i) & 1]++;//x的資訊必須在這裡新增才不會產生影響
	}
	hson = 0;
	if(!keep) {
		modify(x, pre, -1);
	}
}
signed main() { 
	cin >> n;
	memset(cnt, 0, sizeof(cnt));
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for(int i = 1; i <= n - 1; i++) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 0, 1);
	cout << ans;
	return 0;
}