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]
从二进制下高位到低位,从根到叶子的顺序建树(懒得画图了,自己看代码理解下)
然后?
我在没看题解时的思路:
- 从trie的根结点开始向下找,直到遇到分支(因为此时高位是1,高位大的一定大)
- 找到分支后,用BFS+贪心查找最优解(尽量让两个数异或后高位为1)
- 但是,最坏情况下,时间复杂度是可以去到2^30的
因此,看了一波题解
正解:
- O(n)枚举每一个
dis[i]
- 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
*/