P4551 最长异或路径

题目

传送门

思路

别在意这是一道紫题,其实还是能做的

首先要知道:异或运算满足交换律,结合律,a xor a = 0,一个点A到另一个点B的异或路径长度等于(A到C的异或路径长度 xor B到C的异或路径长度),其中C为任一点

为什么?

假设C是树的根,后者只是比前者多跑了2遍C到lca(A,B)的路径,也就是这条路径上的边会被异或两边,又因为同一个数异或的结果为0,所以这多跑的2遍对结果无影响

所以,我们随便选一个点作为根(这里就用1号点),求出所有点到1号点的异或路径长度,存在dis[]中,这样,我们就能O(1)求出两个点之间的异或路径长度

到此,原问题转化为:

找一对i,j,使dis[i] ^ dis[j]最大(" ^ "表示异或)

01trie是解决这种异或问题的利器,但是,怎么找呢?

先说01trie:按照dis[i]从二进制下高位到低位,从根到叶子的顺序建树(懒得画图了,自己看代码理解下)

然后?

我在没看题解时的思路:

  1. 从trie的根结点开始向下找,直到遇到分支(因为此时高位是1,高位大的一定大)
  2. 找到分支后,用BFS+贪心查找最优解(尽量让两个数异或后高位为1)
  3. 但是,最坏情况下,时间复杂度是可以去到2^30的

因此,看了一波题解

正解:

  1. O(n)枚举每一个dis[i]
  2. O(30)在trie中贪心查找另一个trie[j],使trie[i] ^ trie[j]最大(这里的贪心其实就是让异或出来的结果高位更大,这也就决定了如何建trie树)

反思

其实我的思路离正解已经很近了,可以说只差了最后一步,但是失之毫厘差之千里,复杂的几乎就是O(n^2)的纯暴力和正解的区别,应该从多方面思考问题的解,优化程序中复杂度最高的地方

代码

#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
	int re = 0 , sig = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')sig = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re * sig;
}
struct ednode{//链式前向星
	int nxt , w , to;
}ed[nn * 2];
int head[nn];
inline void addedge(int u , int v , int w) {
	static int top = 1;
	ed[top].to = v , ed[top].w = w , ed[top].nxt = head[u] , head[u] = top;
	++top;
}

int dis[nn];
int n;
int trie[nn * 30][3];


void dfs(int x , int pre) {//处理出dis数组
	for(int i = head[x] ; i ; i = ed[i].nxt) {
		if(ed[i].to == pre)continue;
		dis[ed[i].to] = dis[x] ^ ed[i].w;
		dfs(ed[i].to , x);
	}
}
void build() {//建trie树
	int top = 1;
	for(int i = 1 ; i <= n ; i++) {
		int tmp = dis[i];
		int p = 1;
		for(int j = 30 ; j >= 0 ; j--) {
			int x = (tmp >> j) & 1;
			if(trie[p][x] == 0)
				trie[p][x] = ++top;
			p = trie[p][x];
		}
	}
}
int GetAns() {
	int ans = 0;
	for(int i = 1 ; i <= n ; i++) {//枚举每一个dis
		int tmp = dis[i];
		int res = 0;
		int p = 1;
		for(int j = 30 ; j >= 0 ; j--) {//找到最优的另一个dis,满足它和dis[i]的异或值最大
			if(trie[p][!((tmp >> j) & 1)] != 0) {
				res += (1 << j);
				p = trie[p][!((tmp >> j) & 1)];
			}
			else
				p = trie[p][(tmp >> j) & 1];
		}
		if(res > ans)
			ans = res;
	}
	return ans;
}

int main() {
	n = read();
	for(int i = 1 ; i < n ; i++) {
		int u , v , w;
		u = read();	v = read();	w = read();
		addedge(u , v , w);
		addedge(v , u , w);
	}
	dfs(1 , 0);
	build();
	cout << GetAns();
	return 0;
}
/*洛谷样例2
10
1 2 12188248
2 3 2060207469
1 4 960096258
1 5 681126748
3 6 719580677
6 7 2084644229
4 8 730246277
1 9 668729523
9 10 1055107866

2084644229

*/
posted @ 2020-11-23 19:38  追梦人1024  阅读(112)  评论(0编辑  收藏  举报