POJ - 3764 The xor-longest Path 01字典树解决最大异或问题

原题: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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值