首先二分答案,设为 \(mid\)。
现在的问题是:若 \(a_i\oplus a_j\geq mid\),则 \(i,j\) 之间有一条连边,判断是否存在一种选边方式使得每个点都恰好在一个简单环上(可以是自环或二元环)。
这个判定条件有点奇怪,一开始感觉有些性质,考场上除了想到只能是奇环或二元环就没想到啥了……
结果是一个妙妙转化,简单环可以看成置换,那么现在问题就变成了:是否存在一个 \(1\sim n\) 的排列 \(p\),使得 \(\forall i,a_i\oplus a_{p_i}\geq mid\)。
如果我们建一个二分图,然后若 \(a_i\oplus a_j \geq mid\) 则让左边的 \(i\) 向右边的 \(j\) 连一条边,那么现在问题就变成了这个二分图是否存在完美匹配。
Hall 定理:
对于一个二分图 \(A,B\)(\(|A|\leq |B|\)),设 \(To(S)\) 为与点集 \(S\) 相连的所有点组成的点集,那么这个二分图存在完美匹配(\(A\) 中的点全部被匹配)当且仅当:对于任意一个 \(A\) 的子集 \(S\),均有 \(|To(S)|\geq |S|\)。
根据浩二定理,我们只需要求出 \(|To(S)|-|S|\) 的最小值然后判断即可,但我们显然不能把边全部建出来(\(O(n^2)\) 级别)。
一种很妙的做法是先把所有的 \(a_i\) 插入 Trie 树中,然后设 \(f_{i,j}\) 表示仅考虑二分图左边在 \(i\) 子树内的点(即在 \(i\) 子树中的 \(a_i\))和右边在 \(j\) 子树内的点构成的导出子图,求出这个子图 \(|To(S)|-|S|\) 的最小值。
这里保证 \(i,j\) 在 Trie 树的同一层中且 \(i,j\) 之前的层异或起来和 \(mid\) 是一样的。
换言之,假设 \(i,j\) 都在第 \(k\) 层(即 \(i,j\) 的左右儿子都是按二进制下第 \(k\) 位是 \(0/1\) 来分的),那么 \(i,j\) 的第 \(k+1\) 位到最高位异或起来和 \(mid\) 的第 \(k+1\) 位到最高位是一样的,即我们尚不清楚 \(i\) 子树内的点和 \(j\) 子树内的点的连边关系。
那么我们最后要求的就是 \(f_{rt,rt}\)。
考虑转移,设 \(l_i,r_i\) 分别表示 \(i\) 的左儿子(\(0\) 边)和右儿子(\(1\) 边),\(l_j,r_j\) 同理。设 \(sz_u\) 表示 Trie 树上 \(u\) 子树内包含多少个 \(a_i\)。
按 \(mid\) 的第 \(k\) 位分类讨论:
-
若为 \(0\),则 \(l_i\) 内的任何一个点都对 \(r_j\) 内的所有点有连边,\(r_i\) 内的任何一个点都对 \(l_j\) 内的所有点有连边。
讨论 \(S\) 的选取范围:
- 若什么都不选,则为 \(0\)。
- 若只在 \(l_i\) 内选,那么 \(To(S)\) 一定包含 \(r_j\) 内的所有点,那么我们只需要让 \(l_i\) 和 \(l_j\) 的导出子图中的 \(|To(S)|-|S|\) 最小,则为 \(sz_{r_j}+f(l_i,l_j)\)。
- 若只在 \(r_i\) 内选,同理可得为 \(sz_{l_j}+f(r_i,r_j)\)。
- 若在 \(l_i,r_i\) 中同时有选,那么 \(To(S)\) 就包含了 \(l_j,r_j\) 内的所有点,是固定的,那么我们只需要让 \(|S|\) 最大即可,则为 \(sz_{l_j}+sz_{r_j}-sz_{l_i}-sz_{r_i}=sz_j-sz_i\)。
-
若为 \(1\),此时尽可能有 \(l_i\) 和 \(r_j\) 之间的连边和 \(r_i\) 和 \(l_j\) 之间的连边,化为两个子问题,有 \(f(i,j)=f(l_i,r_j)+f(r_i,l_j)\)。
观察 DP 的转移,发现这个 DP 事实上是 \(O(n\log V)\) 的(Trie 树大小),感性的理解是把 \(i,j\) 当做两个指针,那么相当于 \(i,j\) 各把整棵 Trie 树遍历了一遍。
看起来严谨一点的证法可以设 \(f(i,j)=O(s_i+s_j)\)(其中 \(s_u\) 表示 Trie 树中 \(u\) 子树的大小),然后根据转移方程归纳证明。
再带上二分,这个算法的时间复杂度为 \(O(n\log^2 V)\) 的,有点卡常,注意一些边界情况。
#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;
}
标签:sz,ch,XSY4231,Trie,mid,int,Hall,dp
From: https://www.cnblogs.com/ez-lcw/p/16842981.html