0-1字典树总结和经典例题(ing)

这篇博客总结了0-1字典树在解决异或最值问题中的应用,并给出了多个经典例题,包括CSU 1216、HDU 4825、HDU 5536、POJ 3764、BZOJ 4260和CodeForces Mail.Ru Cup 2018 Round 2 F Tree and XOR。0-1字典树用于存储二进制串,通过贪心策略寻找与给定数异或结果最大的数。
摘要由CSDN通过智能技术生成

Table of Contents

0-1字典树

例题1. CSU 1216:异或最大值: 给定一些数,任意两个数的最大异或值

例题2. HDU 4825 Xor Sum:每次询问给出一个数,找出一个与它异或结果最大的数

例题3. HDU 5536 Chip Factory: 计算 (s[i] + s[j]) ^ s[k] 的最大值

例题4. POJ 3764 The xor-longest Path : 在树上找一段路径使得异或和最大

例题5. BZOJ 4260: Codechef REBXOR:求两个不相交的区间异或和的最大值

例题6.  CodeForces Mail.Ru Cup 2018 Round 2 F Tree and XOR:找出树上所有路径异或和的第K大


0-1字典树

0-1字典树主要用于解决求异或最值的问题,0-1字典树其实就是一个二叉树,和普通的字典树原理类似,只不过把插入字符改成了插入二进制串的每一位(0或1)。

下面先给出0-1字典树的简单模板:

LL val[32 * MaxN]; //点的值 
int ch[32 * MaxN][2]; //边的值 
int tot; //节点个数 
 
void add(LL x) { //往 01字典树中插入 x 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
        int v = (x >> i) & 1;
        if(!ch[u][v]) { //如果节点未被访问过 
            ch[tot][0] = ch[tot][1] = 0; //将当前节点的边值初始化 
            val[tot] = 0; //节点值为0,表示到此不是一个数 
            ch[u][v] = tot++; //边指向的节点编号 
        }
        u = ch[u][v]; //下一节点 
    }
    val[u] = x; //节点值为 x,即到此是一个数 
}
 
LL query(LL x) { 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
        int v = (x >> i) & 1;
        //利用贪心策略,优先寻找和当前位不同的数 
        if(ch[u][v^1]) u = ch[u][v^1];
        else u = ch[u][v];
    }
    return val[u]; //返回结果 
}

不难发现以下事实:

  • 01字典树是一棵最多32层的二叉树,其每个节点的两条边分别表示二进制的某一位的值为 0 还是为 1。将某个路径上边的值连起来就得到一个二进制串。
  • 节点个数为 1 的层(最高层,也就是根节点)节点的边对应着二进制串的最高位,向下的每一层逐位降低。
  • 以上代码中,ch[i] 表示一个节点,ch[i][0] 和 ch[i][1] 表示节点的两条边指向的节点,val[i] 表示节点的值。
  • 每个节点主要有4个属性:节点值、节点编号、两条边指向的下一节点的编号。
  • 节点值 val 为 0时表示到当前节点为止不能形成一个数,否则 val[i] = 数值。
  • 节点编号在程序运行时生成,无规律。
  • 可通过贪心的策略来寻找与 x 异或结果最大的数,即优先找和 x 二进制的未处理的最高位值不同的边对应的点,这样保证结果最大。

复杂度:O(32*n)

 

例题1. CSU 1216:异或最大值

http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1216

Description

给定一些数,求这些数中两个数的异或值最大的那个值。

(对于一个长度为 n 的数组a1, a2, …, an,请找出不同的 i, j,使 ai ^ aj 的值最大)

Input

多组数据。第一行为数字个数n,1 <= n <= 10 ^ 5。接下来n行每行一个32位有符号非负整数。

Output

任意两数最大异或值

 

Solution

贪心找最大异或值: 

把每一个数以二进制形式从高位到低位插入trie树中,依次枚举每个数,在trie中贪心,即当前为0则向1走,为1则向0走。

异或运算有一个性质,就是对应位不一样则为1,要使结果最大化,就要让越高的位为1,所以找与一个数使得两数的异或结果最大,就需要从树的根结点(也就是最高位)开始找,如果对应位置的这个数是0,优先去找那一位为1的数,否则再找0;同理,如果对应位置的这个数是1,优先去找那一位为0的数,否则再找1。最终找到的数就是跟这个数异或结果最大的数。

对于n个数,每个数找一个这样的数并算出结果求其中的最大值即可。

Code

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define fi first
#define se second
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
const int Mod = 1e9 + 7;
const int MaxN = 1e5 + 5;

LL a[MaxN];
LL val[32 * MaxN]; 
int ch[32 * MaxN][2];  
int tot; 
 
void add(LL x) { 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
        int v = (x >> i) & 1;
        if(!ch[u][v]) {  
            ch[tot][0] = ch[tot][1] = 0;  
            val[tot] = 0;  
            ch[u][v] = tot++; 
        }
        u = ch[u][v]; 
    }
    val[u] = x; 
}
 
LL query(LL x) { 
    int u = 0;
    for(int i = 32; i >= 0; i--) {
        int v = (x >> i) & 1;
        if(ch[u][v^1]) u = ch[u][v^1];
        else u = ch[u][v];
    }
    return val[u];  
}

int main(){
	int n; 
	while(cin >> n) {
		ch[0][0] = ch[0][1] = 0; 
		tot = 1;
		for(int i = 1; i <= n; i++) {
			cin >> a[i];
			add(a[i]);
		}
		LL ans = 0LL;
		for(int i = 1; i <= n; i++) {
			ans = max(ans, a[i] ^ query(a[i]));
		}
		cout << ans << endl;
	}
	return 0;
}</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值