1. 程式人生 > >BZOJ5362: [Lydsy1805月賽]quailty 算法

BZOJ5362: [Lydsy1805月賽]quailty 算法

最小 .com %d pri def string define ans scan

BZOJ5362: [Lydsy1805月賽]quailty 算法

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

分析:

  • 題意即求一個最小基環樹森林,兩點之間邊權為異或值。
  • 這題的思路很好,先排序,我們二進制分組,將\(0\)\(1\)分成兩部分,顯然這兩部分之間的邊能不連就不連。
  • 但也有必須連的情況,就是出現某個集合大小小於等於\(2\)的情況,內部無法自身構成基環樹,需要和另外一個集合連邊,此時我們暴力找最小的邊即可。
  • 時間復雜度為\(O(nlogn)\)

代碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define N 300050
typedef long long ll;
int n,a[N],k=30;
ll ans;
void solve(int l,int r,int d) {
    if(d==-1||l>=r) return ;
    if(l==r-1) {ans+=a[l]^a[r]; return ;}
    int mid=l,i,j;
    for(;mid<=r&&!((a[mid]>>d)&1);mid++) ;
    solve(l,mid-1,d-1);
    solve(mid,r,d-1);
    int ls=mid-l,rs=r-mid+1;
    if(ls>2&&rs>2) return ;
    if(ls<1||rs<1) return ;
    int mn1=1<<30,mn2=1<<30;
    for(i=l;i<mid;i++) {
        for(j=mid;j<=r;j++) {
            int x=a[i]^a[j];
            if(x<mn1) mn2=mn1,mn1=x;
            else if(x<mn2) mn2=x;
        }
    }
    if(ls<=2&&rs<=2) ans+=mn1+mn2;
    else ans+=mn1;
}
int main() {
    int T;
    scanf("%d",&T);
    while(T--) {
        scanf("%d",&n);
        int i;
        for(i=1;i<=n;i++) scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        ans=0;
        solve(1,n,k);
        printf("%lld\n",ans);
    }
}

BZOJ5362: [Lydsy1805月賽]quailty 算法