题目链接

https://www.lydsy.com/JudgeOnline/problem.php?id=5362

思路

对于这样的图,显然我们不能暴力跑二分图匹配。(废话)

观察构造二分图的方法,我们可以发现B中的点总是连两条权值相同的边,而A中的点总是连n1条边。

所以我们可以把B中的点看成连接A中两个点的边,这样我们就构造出了一个n个点,n(n1)2条边的完全图,连接ij的边权值为ai xor aj

匹配的方式?选取尽量多的边,给每条边定向,使得每个点出度1。这样做显然能保证选择的边最多,并且没有重复选点。

考虑环套树森林,如果选择的边恰好构成环套树森林,那么显然能满足要求。

对点权排序显然不影响答案,因此可以将点权排序。

排序后转化为二进制,根据最高位01划分成两个集合,显然选择的边在集合内部较优,递归处理集合内部。

如果集合内部连的边不能构成环套树森林,则暴力枚举两个集合建边。

容易证明,集合内部连的边不能构成环套树森林,当且仅当集合的大小小于等于2,因此暴力枚举时间复杂度为O(S)S为集合大小。

总时间复杂度为O(nloga)

代码

#include <cstdio>
#include <algorithm>

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; i<mid; ++i)
    {
      for(int j=mid; j<=r; ++j)
        {
          int v=a[i]^a[j];
          if(v<mn)
            {
              se=mn;
              mn=v;
            }
          else if(v<se)
            {
              se=v;
            }
        }
    }
  if((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;
}