又是一道並查集。。。最近作的並查集咋這麼多。。。spa
首先,維護元素間關係的題想到並查集。code
由於這裏涉及到「翻轉」操做。因此咱們把反轉事後的點$i$設爲$i'$,令$i'=i+n$。而後使用拆點並查集來計算。blog
咱們把須要同時知足的條件放入一個並查集。而後對於任意兩個串,都有四種狀況:ip
這樣咱們就維護了並查集之間的關係。而題目求的就是在知足上述條件狀況下,把全部字母表明的點都取到,形如$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(); }
對於這類維護元素間兩兩關係(改變一個會同時改變其餘的),很容易使用拆點並查集維護。須要注意的是每當咱們獲得一個信息。咱們須要把這個信息能獲得的全部關係都體如今並查集中。也就是說要把開出來的虛點看成實際點來處理。舉個例子,若是上面的第四種狀況只合並了$i,j$沒有合併$i+n,j+n$。會獲得WA11的好成績。class