ARC122D XOR Game(博弈论?字典树,贪心)

题面

ARC122D XOR Game

黑板上有 2 N 2N 2N 个数,第 i i i 个数为 A i A_i Ai

O I D \rm OID OID(OneInDark) 和 H I D HID HID(HandInDevil) 玩一个游戏,总共进行 N N N 轮,每轮

  • H I D HID HID 选一个黑板上的数擦掉,令其为 x x x
  • O I D \rm OID OID 再选一个黑板上的数擦掉,令其为 y y y
  • 在笔记本上记下 x ⊕ y x\oplus y xy 的值。

N N N 轮游戏之后,黑板上的数字会被擦光,笔记本上会记录有 N N N 个数字。最终的分数即为笔记本上最大的数字。 H I D HID HID 想让分数尽量大, O I D \rm OID OID 想让分数尽量小。在两个人都采取最优策略的情况下,问最终的分数为多少?

1 ≤ N ≤ 2 ⋅ 1 0 5 1\leq N\leq 2\cdot 10^5 1N2105 0 ≤ A i < 2 30 0\leq A_i<2^{30} 0Ai<230

题解

H I D HID HID 演算出最优方案时,他惊恐地发现:不论如何,整个游戏都被 O I D \rm OID OID 玩弄于指尖。

对于任何配对方案, O I D \rm OID OID 都可以做到。不管 H I D HID HID 选的是什么数, O I D \rm OID OID 只需要选与其配对的数就行了。所以我们要求的其实是找一种配对方案,使得异或后最大的值最小。

最大值最小,二分?

没有必要,这道题是求异或的最大值最小,和贪心算法、字典树的契合度极高。

经典的做法,先把所有数都放在字典树上再说。然后从最大的位开始考虑,尽量使得高位异或值 0 \tt0 0 ,我们就赢了。

当你遍历到字典树上某个点时(假设此时子树中有偶数个数字),会有两种情况:

  • 左子树和右子树的数字个数都是偶数,此时肯定可以在两棵子树中分别匹配完,不需要越过该节点匹配,这样是最优的。然后,由于后面的位数不确定,分别遍历左子树和右子树,取最大值
  • 左子树和右子树的数字个数都是奇数,此时不论怎么匹配,总得有一组数字是要越过该节点的,也就是说这一位上免不了有一个 1 \tt1 1 了。那么就在这两棵子树上进行第二类遍历:每次遍历两个点 { x , y } \tt\{x,y\} {x,y},如果能够使当前位为 0 \tt0 0 ,就分别遍历 { x . s o n [ 0 ] , y . s o n [ 0 ] } \tt\{x.son[0],y.son[0]\} {x.son[0],y.son[0]} { x . s o n [ 1 ] , y . s o n [ 1 ] } \tt\{x.son[1],y.son[1]\} {x.son[1],y.son[1]} 取最小值,如果不行(其中不论如何都有一棵子树没有数字时),就只好把返回值加上这一位的 1 \tt1 1,再遍历 { x . s o n [ 0 ] , y . s o n [ 1 ] } \tt\{x.son[0],y.son[1]\} {x.son[0],y.son[1]} { x . s o n [ 1 ] , y . s o n [ 0 ] } \tt\{x.son[1],y.son[0]\} {x.son[1],y.son[0]} 取最小值加上。

最后的返回值就是答案了。

复杂度 Θ ( N   l o g   A ) \tt \Theta(N~log~A) Θ(N log A)

CODE

#include<set>
#include<queue>
#include<bitset>
#include<cmath>
#include<ctime>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 400005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define INF 0x3f3f3f3f
#define SI set<int>::iterator
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
int a[MAXN];
int tre[MAXN*30][2],cnt = 1;
int siz[MAXN*30];
void add(int x) {
	int p = 1; siz[p] ++;
	for(int i = 29;i >= 0;i --) {
		int d = (x & (1<<i)) ? 1:0;
		if(!tre[p][d]) tre[p][d] = ++ cnt;
		p = tre[p][d]; siz[p] ++;
	}return ;
}
int solve(int i,int a,int b,bool fl) {
	if(i < 0) return 0;
	if(!fl && siz[a] == 0) return 0;
	if(siz[a] == 0 || siz[b] == 0) return (1<<(i+1))-1;
	if(a == b) {
		if(siz[tre[a][0]] & 1) {
			return solve(i-1,tre[a][0],tre[a][1],1)+(1<<i);
		}
		return max(solve(i-1,tre[a][0],tre[a][0],0),solve(i-1,tre[a][1],tre[a][1],0));
	}
	else {
		if((siz[tre[a][0]] > 0 && siz[tre[b][0]] > 0) || (siz[tre[a][1]] && siz[tre[b][1]])) {
			return min(solve(i-1,tre[a][0],tre[b][0],1),solve(i-1,tre[a][1],tre[b][1],1));
		}
		return min(solve(i-1,tre[a][1],tre[b][0],1),solve(i-1,tre[a][0],tre[b][1],1)) + (1<<i);
	}
}
int main() {
	n = read();
	for(int i = 1;i <= 2*n;i ++) {
		a[i] = read();
		add(a[i]);
	}
	printf("%d\n",solve(29,1,1,0));
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值