两道可持久化字典树的模板题(HDU - 4757和HDU - 6191 )

两道题都是树上异或最大值的问题,这一类问题都是可持久化01字典树的模板题,不过需要一些树论知识来处理一下。

HDU - 4757:
求树上两点路径间的异或最大值。
显而易见,两点路径就是个lca。
对每一个节点,以其父亲为last版本,新建可持久化字典树即可。
最后用(sz[next[now][!d]] + sz[next[now2][!d]]) - (2 * sz[next[now3][!d]])是否大于0来判断下一步往哪边走,这是一个lca中常见的容斥操作。
这样会漏掉考虑lca(减掉了),所以在最后单独考虑。
lca使用倍增实现。

ac代码:

#include
using namespace std;

const int maxn = 1e5 + 5;

vector<int> G[maxn];
int n, m, num[maxn];

struct Trie {
	int tot;
	int next[maxn * 30][2], root[maxn];
	int sz[maxn * 30];
	int p[maxn][20], dep[maxn];

	int newNode() {
		next[tot][0] = next[tot][1] = 0;
		return tot++;
	}

	void insert(int u, int fa, int x) {
		int now = root[u], last = root[fa];
		for(int i = 16; i >= 0; i--) {
			int d = (x >> i) & 1;
			next[now][d] = newNode();
			next[now][!d] = next[last][!d];
			now = next[now][d];
			last = next[last][d];
			sz[now] = sz[last] + 1;
		}
	}

	void dfs(int u, int fa) {
		root[u] = newNode();
		insert(u, fa, num[u]);
		p[u][0] = fa;
		dep[u] = dep[fa] + 1;
		for(int i = 0; i < G[u].size(); i++) {
			int v = G[u][i];
			if(v == fa) {
				continue;
			}
			dfs(v, u);
		}
	}

	int lca(int u, int v) {
		if(dep[u] > dep[v]) swap(u, v);
		for(int i = 0; i < 20; i++) {
			if((dep[v] - dep[u]) >> i & 1) {
				v = p[v][i];
			}
		}
		if(v == u) return u;
		for(int i = 20 - 1; i >= 0; i--) {
			if(p[u][i] != p[v][i]) {
				u = p[u][i];
				v = p[v][i];
			}
		}
		return p[u][0];
	}


	void build() {
		tot = 1;
		dfs(1, 0);
		for(int i = 1; i < 20; i++) {
			for(int j = 1; j <= n; j++) {
				p[j][i] = p[p[j][i - 1]][i - 1];
			}
		}
	}

	int query(int u, int v, int x) {
		int k = lca(u, v);
		int ans = 0;
		int now = root[u], now2 = root[v], now3 = root[k];
		for(int i = 16; i >= 0; i--) {
			int d = (x >> i) & 1;
			int check = (sz[next[now][!d]] + sz[next[now2][!d]]) - (2 * sz[next[now3][!d]]);
			if(check > 0) {
				ans += (1 << i);
				d = !d;
			}
			now = next[now][d];
			now2 = next[now2][d];
			now3 = next[now3][d];
		}
		return max(ans, x ^ num[k]);
	}
	

} trie;

/*
11 99
2 5 5 7 1 9 6 4 2 2 4
1 2
2 3
1 4
4 5
5 8
8 9
4 6
6 7
9 10
9 11


*/


int main() {
	while(~scanf("%d%d", &n, &m)) {
		for(int i = 1; i <= n; i++) {
			scanf("%d", &num[i]);
			G[i].clear();
		}

		int u, v;
		for(int i = 1; i <= n - 1; i++) {
			scanf("%d%d", &u, &v);
			G[u].push_back(v);
			G[v].push_back(u);
		}

		trie.build();

		int x, y, z;
		while(m--) {
			scanf("%d%d%d", &x, &y, &z);
			printf("%d\n", trie.query(x, y, z));
		}
	}
	return 0;
}

HDU - 6191 :
这题是求所有子节点的异或最大值。
呃,那又很显然,这时候自然要上把树拍成区间的dfs序了。
求完dfs序之后普通的建可持久化字典树,再普通的像主席树一样做树上差分就完事了。
这题除了写出一个初始化bug找了一年以外打的很顺利。

ac代码:

#include
using namespace std;

const int maxn = 1e5 + 5;

vector<int> G[maxn];
int n, m, num[maxn];

struct Trie {
	int tot, clock;
	int root[maxn], next[maxn * 32][2];
	int sz[maxn * 32];
	int in[maxn], out[maxn], tid[maxn];

	int newNode() {
		next[tot][0] = next[tot][1] = 0;
		return tot++;
	}

	void insert(int now, int last, int x) {
		for(int i = 30; i >= 0; i--) {
			int d = (x >> i) & 1;
			next[now][d] = newNode();
			next[now][!d] = next[last][!d];
			now = next[now][d];
			last = next[last][d];
			sz[now] = sz[last] + 1;
		}
	}

	void dfs(int u) {
		in[u] = ++clock;
		tid[in[u]] = u;
		for(int i = 0; i < G[u].size(); i++) {
			dfs(G[u][i]);
		}
		out[u] = clock;
	}

	void init() {
		clock = 0;
		tot = 1;
		dfs(1);
		for(int i = 1; i <= n; i++) {
			root[i] = newNode();
			insert(root[i], root[i - 1], num[tid[i]]);
		}
	}

	int query(int u, int x) {
		int l = root[in[u] - 1], r = root[out[u]];
		int ans = 0;
		for(int i = 30; i >= 0; i--) {
			int d = (x >> i) & 1;
			int check = sz[next[r][!d]] - sz[next[l][!d]];
			if(check > 0) {
				ans += (1 << i);
				d = !d;
			}
			l = next[l][d];
			r = next[r][d];
		}
		return ans;
	}

} trie;

/*
9 99
1 1 1 1 1 1 1 1 1
1 2 1 4 4 6 6 8


*/

int main() {
	while(~scanf("%d%d", &n, &m)) {
		for(int i = 1; i <= n; i++) {
			scanf("%d", &num[i]);
			G[i].clear();
		}

		int fa;
		for(int i = 2; i <= n; i++) {
			scanf("%d", &fa);
			G[fa].push_back(i);
		}

		trie.init();

		int u, x;
		while(m--) {
			scanf("%d%d", &u, &x);
			printf("%d\n", trie.query(u, x));
		}
	}
	return 0;
}

打完了题感觉自己的可持久化trie板子有点慢,明天去扒个快的板子下来。

发现一个非常有意思的事实。
next数组里存的索引很有可能跟sz数组有某种等价关系…
把上面两份代码里的sz数组相关全部删掉之后竟然都ac了。

你可能感兴趣的:(ACM,LCA,可持久化字典树,模板集合)