Codeforces 1250E The Coronation

解题思路

用2-SAT的思路将题目转化为:已知\(n\)个二元组\(<x,y>\),可以算出有多少属于不同二元组的元素\((a,b)\)存在冲突,要在每个二元组\(<x,y>\)中选择选择一个元素,且要尽可能的少选\(y\),问是否可以选取\(n\)个两两不相互矛盾的元素,若可以输出选取方案。

经过简单的推导可以得到,对于\(<x_i,y_i>\)\(<x_j,y_j>\)

  • \(x_ix_j\)(不)冲突,则\(y_iy_j\)(不)冲突
  • \(x_iy_j\)(不)冲突,则\(y_ix_j\)(不)冲突

继续用2-SAT的思路,并结合上面的性质:

  • \(x_i\)仅和\(x_j\)冲突,则从\(x_i\)\(y_j\)连一条边,从\(x_j\)\(y_i\)连一条边;
  • \(x_i\)仅和\(y_j\)冲突,则从\(x_i\)\(x_j\)连一条边,从\(y_i\)\(y_j\)连一条边;
  • 若都不冲突则不连边;
  • 若都冲突则无解。

这样一来,我们得到了一个或多个联通块,根据之前推导的性质,对于第\(i\)个二元组,如果\(i\)的选取方法确定了,那么相同联通块里的选取方法也就都确定了,再加上不同联通块之间是互不影响的,我们只需要对每个联通块贪心的选取\(y\)少的方案,然后把所有联通块的答案加起来就是最终的答案了。

跑Tarjan缩点或者并查集缩点维护一下就没了。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110;
int n,m,k;
ll a[55],b[55]; char s[55];

int fa[N],sz[N]; bool vis[N];
int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
void merge(int x,int y){
    x=find(x); y=find(y);
    if(x!=y){sz[y]+=sz[x];fa[x]=y;} 
}

inline int getsame(ll x){
    int cnt=0;
    while(x){cnt++;x-=x&(-x);}
    return m-cnt;   
}

void solve(){
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            bool c1=false,c2=false;
            if(getsame(a[i]^a[j])>=k)c1=true;
            if(getsame(a[i]^b[j])>=k)c2=true;
            if(c1 && c2)continue;
            if(!c1 && !c2){printf("-1\n");return;}
            if(c2)merge(i,j+n),merge(i+n,j);
            if(c1)merge(i,j),merge(i+n,j+n);
        }
    }
    vector<int>ans;
    for(int i=1;i<=n;i++){
        if(find(i)==find(i+n)){printf("-1\n");return;}
        int fi=find(i),fin=find(i+n);
        if(vis[fi])continue;
        if(vis[fin]){ans.push_back(i);continue;}
        if(sz[fi]>sz[fin]){vis[fin]=true;ans.push_back(i);}
        else vis[fi]=true;
    }
    printf("%d\n",(int)ans.size());
    for(int i:ans)printf("%d ",i); puts("");
}

int main()
{   
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d %d %d",&n,&m,&k);
        for(int i=1;i<=2*n;i++)fa[i]=i, sz[i]=(i>n?1:0), vis[i]=false;  
        for(int i=1;i<=n;i++){
            scanf("%s",s); a[i]=0; b[i]=0;
            for(int j=0;s[j];j++)a[i]*=2, a[i]+=s[j]-'0';
            reverse(s,s+m);
            for(int j=0;s[j];j++)b[i]*=2, b[i]+=s[j]-'0';
        }
        solve();
    }
    return 0;   
}

猜你喜欢

转载自www.cnblogs.com/zengzk/p/11774349.html