[Codeforces1250E] The Coronation

[Codeforces1250E] The Coronation

The Coronationios

又是一道並查集。。。最近作的並查集咋這麼多。。。spa

思路

首先,維護元素間關係的題想到並查集。code

由於這裏涉及到「翻轉」操做。因此咱們把反轉事後的點$i$設爲$i'$,令$i'=i+n$。而後使用拆點並查集來計算。blog

咱們把須要同時知足的條件放入一個並查集。而後對於任意兩個串,都有四種狀況:ip

  1. 兩個串不管轉不轉都不類似,顯然無解,直接輸出-1便可。
  2. 兩個串不管轉不轉都類似,由於這兩個串之間沒有相互牽制的限制,因此無論。
  3. 兩個串類似當且僅當其中一個翻轉。那麼這時候將$i,j+n$合併,將$j,i+n$合併。(表明了若是取$i$ , $j+n$必須取,反之亦然)
  4. 兩個串類似當且僅當沒有一個翻轉,那麼這時候將$i,j$合併,將$i+n,j+n$合併。(理由同上)

這樣咱們就維護了並查集之間的關係。而題目求的就是在知足上述條件狀況下,把全部字母表明的點都取到,形如$i'$的字母取的最少。咱們能夠給並查集加權來維護這個東西。get

又由於對於$i$處於的集合,這個集合和$i'$所在的集合的惟一區別是全部字母取反($i$變成$i'$,$i'$變成$i$)。string

因此對於這兩個集合,不管取哪一個,能取到的字母都是同樣的。而同種字母$i$和$i'$只能取一個。因此取哪一個集合效果等價,那咱們就貪心的取權值最小的那個集合。it

具體看代碼實現吧。io

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
int map[55][55],size[105],n,m,k,fa[105];
bool visit[105];
char s[55];//i=keep i'=i+n=reverse
vector<int>ans;
int check(int u,int v) {
    int flag=0;int m1=0,m2=0;
    for(int i=1;i<=m;i++){if(map[u][i]^map[v][i])flag++;}
    if(flag<=k){m1=1;}
    flag=0;
    for(int i=1,j=m;i<=m;i++,j--){if(map[u][i]^map[v][j])flag++;}
    if(flag<=k){m2=1;}
    if(m1&&m2)return 0;
    else if(m1&&!m2)return 2;
    else if(!m1&&m2)return 1;
    else return -1;
}
int get(int x){return (fa[x]==x)?x:(fa[x]=get(fa[x]));}
void uni(int x,int y){size[get(y)]+=size[get(x)];fa[get(x)]=get(y);}
void solve() {
    ans.clear();
    memset(map,0,sizeof(map));
    memset(size,0,sizeof(size));
    memset(visit,0,sizeof(visit));
    scanf("%d%d%d",&n,&m,&k);k=m-k;
    for(int i=1;i<=n;i++) {
        scanf("%s",s+1);
        for(int j=1;j<=m;j++)map[i][j]=(s[j]=='1')?1:0;
    }
    for(int i=1;i<=2*n;i++)fa[i]=i;
    for(int i=n+1;i<=2*n;i++)size[i]=1;
    for(int i=1;i<=n;i++) {
        for(int j=i+1;j<=n;j++) {
            int tmp=check(i,j);
            if(tmp==-1){printf("-1\n");return;}
            else if(tmp==1){
                if(get(i)==get(i+n)||get(j)==get(j+n)){printf("-1\n");return;}
                if(get(i)!=get(j+n)){uni(i,j+n);}
                if(get(j)!=get(i+n)){uni(j,i+n);}
            }
            else if(tmp==2){if(get(i)!=get(j))uni(i,j);if(get(i+n)!=get(j+n))uni(i+n,j+n);}
        }
    }
    for(int i=1;i<=n;i++) {
        if(get(i)==get(i+n)){printf("-1\n");return;}
        else if(visit[get(i)])continue;
        else if(visit[get(i+n)]){ans.push_back(i);continue;}
        else if(size[get(i)]>size[get(i+n)]){visit[get(i+n)]=1;ans.push_back(i);}
        else {visit[get(i)]=1;}
    }
    printf("%d\n",ans.size());
    for(int i=0;i<ans.size();i++)printf("%d ",ans[i]);
    printf("\n");
}
int main() {
    int t;scanf("%d",&t);
    while(t--)solve();
}

Tips

對於這類維護元素間兩兩關係(改變一個會同時改變其餘的),很容易使用拆點並查集維護。須要注意的是每當咱們獲得一個信息。咱們須要把這個信息能獲得的全部關係都體如今並查集中。也就是說要把開出來的虛點看成實際點來處理。舉個例子,若是上面的第四種狀況只合並了$i,j$沒有合併$i+n,j+n$。會獲得WA11的好成績。class

相關文章
相關標籤/搜索