题目链接
https://www.lydsy.com/JudgeOnline/problem.php?id=5362
思路
对于这样的图,显然我们不能暴力跑二分图匹配。(废话)
观察构造二分图的方法,我们可以发现BB中的点总是连两条权值相同的边,而AA中的点总是连n−1n−1条边。
所以我们可以把B中的点看成连接AA中两个点的边,这样我们就构造出了一个nn个点,n(n−1)2n(n−1)2条边的完全图,连接ii和jj的边权值为ai xor ajai xor aj。
匹配的方式?选取尽量多的边,给每条边定向,使得每个点出度≤1≤1。这样做显然能保证选择的边最多,并且没有重复选点。
考虑环套树森林,如果选择的边恰好构成环套树森林,那么显然能满足要求。
对点权排序显然不影响答案,因此可以将点权排序。
排序后转化为二进制,根据最高位00或11划分成两个集合,显然选择的边在集合内部较优,递归处理集合内部。
如果集合内部连的边不能构成环套树森林,则暴力枚举两个集合建边。
容易证明,集合内部连的边不能构成环套树森林,当且仅当集合的大小小于等于22,因此暴力枚举时间复杂度为O(S)O(S),SS为集合大小。
总时间复杂度为O(nloga)O(nloga)。
代码
#include
#include
const int maxn=300000;
const int inf=0x3f3f3f3f;
int read()
{
int x=0,f=1;
char ch=getchar();
while((ch<'0')||(ch>'9'))
{
if(ch=='-')
{
f=-f;
}
ch=getchar();
}
while((ch>='0')&&(ch<='9'))
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int n,a[maxn+10],t;
long long ans;
int solve(int hi,int l,int r)
{
if(hi==-1)
{
return 0;
}
if(r-l==0)
{
return 0;
}
if(r-l==1)
{
ans+=a[l]^a[r];
return 0;
}
int mid=l;
while((((a[mid]>>hi)&1)==0)&&(mid<=r))
{
++mid;
}
if(mid!=l)
{
solve(hi-1,l,mid-1);
}
if(mid<=r)
{
solve(hi-1,mid,r);
}
if((mid==l)||(mid>r))
{
return 0;
}
int lsize=mid-l,rsize=r-mid+1;
if((lsize>=3)&&(rsize>=3))
{
return 0;
}
int mn=inf,se=inf;
for(int i=l; ifor(int j=mid; j<=r; ++j)
{
int v=a[i]^a[j];
if(velse if(vif((lsize<=2)&&(rsize<=2))
{
ans+=mn+se;
}
else
{
ans+=mn;
}
return 0;
}
inline int work()
{
n=read();
for(int i=1; i<=n; ++i)
{
a[i]=read();
}
ans=0;
std::sort(a+1,a+n+1);
solve(30,1,n);
printf("%lld\n",ans);
return 0;
}
int main()
{
t=read();
while(t--)
{
work();
}
return 0;
}