【XSY4231】人赢(图论,Hall定理,Trie树,树形DP)

首先二分答案,设为 m i d mid mid

现在的问题是:若 a i ⊕ a j ≥ m i d a_i\oplus a_j\geq mid aiajmid,则 i , j i,j i,j 之间有一条连边,判断是否存在一种选边方式使得每个点都恰好在一个简单环上(可以是自环或二元环)。

这个判定条件有点奇怪,一开始感觉有些性质,考场上除了想到只能是奇环或二元环就没想到啥了……

结果是一个妙妙转化,简单环可以看成置换,那么现在问题就变成了:是否存在一个 1 ∼ n 1\sim n 1n 的排列 p p p,使得 ∀ i , a i ⊕ a p i ≥ m i d \forall i,a_i\oplus a_{p_i}\geq mid i,aiapimid

如果我们建一个二分图,然后若 a i ⊕ a j ≥ m i d a_i\oplus a_j \geq mid aiajmid 则让左边的 i i i 向右边的 j j j 连一条边,那么现在问题就变成了这个二分图是否存在完美匹配。

Hall 定理:

对于一个二分图 A , B A,B A,B ∣ A ∣ ≤ ∣ B ∣ |A|\leq |B| AB),设 T o ( S ) To(S) To(S) 为与点集 S S S 相连的所有点组成的点集,那么这个二分图存在完美匹配( A A A 中的点全部被匹配)当且仅当:对于任意一个 A A A 的子集 S S S,均有 ∣ T o ( S ) ∣ ≥ ∣ S ∣ |To(S)|\geq |S| To(S)S

根据浩二定理,我们只需要求出 ∣ T o ( S ) ∣ − ∣ S ∣ |To(S)|-|S| To(S)S 的最小值然后判断即可,但我们显然不能把边全部建出来( O ( n 2 ) O(n^2) O(n2) 级别)。

一种很妙的做法是先把所有的 a i a_i ai 插入 Trie 树中,然后设 f i , j f_{i,j} fi,j 表示仅考虑二分图左边在 i i i 子树内的点(即在 i i i 子树中的 a i a_i ai)和右边在 j j j 子树内的点构成的导出子图,求出这个子图 ∣ T o ( S ) ∣ − ∣ S ∣ |To(S)|-|S| To(S)S 的最小值。

这里保证 i , j i,j i,j 在 Trie 树的同一层中且 i , j i,j i,j 之前的层异或起来和 m i d mid mid 是一样的。

换言之,假设 i , j i,j i,j 都在第 k k k 层(即 i , j i,j i,j 的左右儿子都是按二进制下第 k k k 位是 0 / 1 0/1 0/1 来分的),那么 i , j i,j i,j 的第 k + 1 k+1 k+1 位到最高位异或起来和 m i d mid mid 的第 k + 1 k+1 k+1 位到最高位是一样的,即我们尚不清楚 i i i 子树内的点和 j j j 子树内的点的连边关系。

那么我们最后要求的就是 f r t , r t f_{rt,rt} frt,rt

考虑转移,设 l i , r i l_i,r_i li,ri 分别表示 i i i 的左儿子( 0 0 0 边)和右儿子( 1 1 1 边), l j , r j l_j,r_j lj,rj 同理。设 s z u sz_u szu 表示 Trie 树上 u u u 子树内包含多少个 a i a_i ai

m i d mid mid 的第 k k k 位分类讨论:

  • 若为 0 0 0,则 l i l_i li 内的任何一个点都对 r j r_j rj 内的所有点有连边, r i r_i ri 内的任何一个点都对 l j l_j lj 内的所有点有连边。

    讨论 S S S 的选取范围:

    • 若什么都不选,则为 0 0 0
    • 若只在 l i l_i li 内选,那么 T o ( S ) To(S) To(S) 一定包含 r j r_j rj 内的所有点,那么我们只需要让 l i l_i li l j l_j lj 的导出子图中的 ∣ T o ( S ) ∣ − ∣ S ∣ |To(S)|-|S| To(S)S 最小,则为 s z r j + f ( l i , l j ) sz_{r_j}+f(l_i,l_j) szrj+f(li,lj)
    • 若只在 r i r_i ri 内选,同理可得为 s z l j + f ( r i , r j ) sz_{l_j}+f(r_i,r_j) szlj+f(ri,rj)
    • 若在 l i , r i l_i,r_i li,ri 中同时有选,那么 T o ( S ) To(S) To(S) 就包含了 l j , r j l_j,r_j lj,rj 内的所有点,是固定的,那么我们只需要让 ∣ S ∣ |S| S 最大即可,则为 s z l j + s z r j − s z l i − s z r i = s z j − s z i sz_{l_j}+sz_{r_j}-sz_{l_i}-sz_{r_i}=sz_j-sz_i szlj+szrjszliszri=szjszi
  • 若为 1 1 1,此时尽可能有 l i l_i li r j r_j rj 之间的连边和 r i r_i ri l j l_j lj 之间的连边,化为两个子问题,有 f ( i , j ) = f ( l i , r j ) + f ( r i , l j ) f(i,j)=f(l_i,r_j)+f(r_i,l_j) f(i,j)=f(li,rj)+f(ri,lj)

观察 DP 的转移,发现这个 DP 事实上是 O ( n log ⁡ V ) O(n\log V) O(nlogV) 的(Trie 树大小),感性的理解是把 i , j i,j i,j 当做两个指针,那么相当于 i , j i,j i,j 各把整棵 Trie 树遍历了一遍。

看起来严谨一点的证法可以设 f ( i , j ) = O ( s i + s j ) f(i,j)=O(s_i+s_j) f(i,j)=O(si+sj)(其中 s u s_u su 表示 Trie 树中 u u u 子树的大小),然后根据转移方程归纳证明。

再带上二分,这个算法的时间复杂度为 O ( n log ⁡ 2 V ) O(n\log^2 V) O(nlog2V) 的,有点卡常,注意一些边界情况。

#include<bits/stdc++.h>

#define N 500010

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

const int V=30*N;

int n,a[N];
int node=1,ch[V][2],size[V];

void insert(int x)
{
	int u=1;
	size[u]++;
	for(int i=29;i>=0;i--)
	{
		bool v=(x>>i)&1;
		if(!ch[u][v]) ch[u][v]=++node;
		u=ch[u][v];
		size[u]++;
	}
}

int mid;

int dp(int a,int b,int k)
{
	if(!a) return 0;
	if(!b) return -size[a];
	if(k<0) return min(0,size[b]-size[a]);
	const int la=ch[a][0],ra=ch[a][1];
	const int lb=ch[b][0],rb=ch[b][1];
	if((mid>>k)&1) return dp(la,rb,k-1)+dp(ra,lb,k-1);
	return min(min(0,size[b]-size[a]),min(size[rb]+dp(la,lb,k-1),size[lb]+dp(ra,rb,k-1)));
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		insert(a[i]);
	}
	int l=0,r=(1<<30)-1,ans;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(dp(1,1,29)>=0) ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值