[Lydsy1805月赛] quailty 算法
稍微建一下模型就可以发现,题目要求的其实是一个最小异或基环森林。。。。
可以用类似最小生成树的拟阵性质来证明,贪心的从小的边权开始依次尝试加入的方法是对的。
所以我们把a[]排完序之后直接递归贪心就行了。。。。
从大的位到小的位考虑,能不选这一位为一的边就不选。
首先如果区间内不存在这一位为1的边的话是可以不选的;如果把区间内这一位为1的数分成两个子区间(一定是一个前缀一个后缀),并且子区间内元素都>2的话,也是可以不选的;
其他情况是要选的,可以根据两边子区间的大小来判断选前几小的边。。。
因为只有一边点数<=2的时候需要 O(左右子区间大小乘积) 的复杂度去计算,所以复杂度是对的。。。。
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=300005; int n,a[N],T; ll ans=0; inline int read(){ int x=0; char ch=getchar(); for(;!isdigit(ch);ch=getchar()); for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; return x; } void solve(int o,int l,int r){ if(o<0||l>=r) return; if(r==l+1) { ans+=a[l]^a[r]; return;} int mid; for(mid=l;mid<=r&&((a[mid]>>o)&1)==0;mid++); solve(o-1,l,mid-1),solve(o-1,mid,r); if((mid-l>=3&&r-mid>=2)||mid==l||mid>r) return; int M=1<<30,m=1<<30,now; for(int i=l;i<mid;i++) for(int j=mid;j<=r;j++){ now=a[i]^a[j]; if(now<M) m=M,M=now; else if(now<m) m=now; } if(mid-l<=2&&r-mid<=1) ans+=m+(ll)M; else ans+=M; } int main(){ T=read(); while(T--){ n=read(),ans=0; for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1); solve(30,1,n); printf("%lld\n",ans); } return 0; }
我爱学习,学习使我快乐