首先二分答案,设为 m i d mid mid。
现在的问题是:若 a i ⊕ a j ≥ m i d a_i\oplus a_j\geq mid ai⊕aj≥mid,则 i , j i,j i,j 之间有一条连边,判断是否存在一种选边方式使得每个点都恰好在一个简单环上(可以是自环或二元环)。
这个判定条件有点奇怪,一开始感觉有些性质,考场上除了想到只能是奇环或二元环就没想到啥了……
结果是一个妙妙转化,简单环可以看成置换,那么现在问题就变成了:是否存在一个 1 ∼ n 1\sim n 1∼n 的排列 p p p,使得 ∀ i , a i ⊕ a p i ≥ m i d \forall i,a_i\oplus a_{p_i}\geq mid ∀i,ai⊕api≥mid。
如果我们建一个二分图,然后若 a i ⊕ a j ≥ m i d a_i\oplus a_j \geq mid ai⊕aj≥mid 则让左边的 i i i 向右边的 j j j 连一条边,那么现在问题就变成了这个二分图是否存在完美匹配。
Hall 定理:
对于一个二分图 A , B A,B A,B( ∣ A ∣ ≤ ∣ B ∣ |A|\leq |B| ∣A∣≤∣B∣),设 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+szrj−szli−szri=szj−szi。
-
若为 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;
}