原题:http://poj.org/problem?id=3764
题意:给定一棵N个节点的数,边有权值,随机选择两个点x,y,将他们路径的边上的所有边异或起来,求这样可以达到的最大值。
本来不想写这篇博客的,但是
不写似乎对不起我一个上午的自闭。
好吧先说在前面,造成这一惨烈战况的原因是,我使用了vector将边封装了起来。。
(我真是服了,内存溢出oj上显示RE,然后改了半天)
首先做这道题之前需要一点前导知识,就是给N(N<10^5)个数(大小0~2^31),选出两个数进行异或,能得到最大的结果是多少?
朴素的做法是N^2的复杂度,本题需要用Trie字典树,将二进制每一位插入字典树中,向下搜素的时候搜索其相反的数。0就搜1,1就搜0即可。
我之前也有一篇关于Trie字典树的博客 https://blog.csdn.net/weixin_43191865/article/details/89890262
然后到本题:
本题需要一个小小的转化,就是比如有一颗树
a--b--c--d--e
那么ce中的边的异或值等于( ae中的异或值 异或 ac中的异或值)。
我们用ab 表示 ab中的边的权值
ce=cd^ce=ae^ac=ab^bc^cd^de^ab^bc
注意 ab^ab =0 ,所以可以削掉ab,bc
所以结果为cd^de = ce
那么我们用一个数组h[i]来存所有异或前缀和,然后对于x,y两点,他们的值为h[x]^h[y]
问题就转化为前导知识了,一共有N个数,找出其中两个数使其异或的值最大。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6;
struct NODE {
int to, val;
};
//vector<NODE> node[maxn]; // 用这个会RE
int trie[100006 * 33][3], ind, mx; // ind为建trie用的,mx记录h[i]中的最大值
int h[maxn]; //这个要初始化
int n, tot;
int head[maxn], val[maxn], to[maxn], nxt[maxn];
void BuildTree(int fat, int now, int c) { // 构建h数组
for (int i = head[now]; i != -1; i=nxt[i]) {
if (to[i] == fat) continue;
h[to[i]] = c ^ val[i];
mx = max(mx, h[to[i]]);
BuildTree(now, to[i], h[to[i]]);
}
}
void add_edge(int a,int b,int c) {
nxt[++tot] = head[a]; head[a] = tot; to[tot] = b; val[tot] = c;
nxt[++tot] = head[b]; head[b] = tot; to[tot] = a; val[tot] = c;
}
int main() {
while (scanf("%d", &n) != EOF) {
//memset(trie, 0, sizeof(trie));
tot = 0;
memset(head, -1, sizeof(head));
memset(h, 0, sizeof(h));
mx = 0;
for (int i = 1; i < n; i++) {
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
add_edge(a, b, c);
}
BuildTree(-1, 0, 0); //构建h数组
int end = 0;
ind = 1;
trie[ind][0] = trie[ind][1] = 0; //构建trie数组,初始化根节点
while (mx >> end++); //找出h中最大的数二进制下的位数
for (int i = 1; i <= n; i++) {
int now = 1;
for (int j = end - 2; j >= 0; j--) { // end-2 不是 end-1
int num = (h[i] >> j) & 1;
if (!trie[now][num]) {
trie[now][num] = ++ind;
now = ind;
trie[ind][0] = 0;// 相当于memset了其实 ,一边建trie一边初始化
trie[ind][1] = 0;
}
else now = trie[now][num];
}
}
int ans = 0;
for (int i = 1; i <= n; i++) { //一条边不可能会走他自己,假如走自己也是0,对结果无影响,所以不用排除自己走自己
int now = 1, sto = 0; //sto=storage , 记录该次最大的异或值
for (int j = end - 2; j >= 0; j--) {
int num = (h[i] >> j) & 1;
num = !num; //沿着相反边
if (trie[now][num]) {
now = trie[now][num];
sto += (1 << j);
}
else if (trie[now][!num]) now = trie[now][!num]; //假如没有就走一样的咯
else break;
}
ans = max(ans, sto);
}
cout << ans << endl;
}
return 0;
}