又是一道并查集。。。最近作的并查集咋这么多。。。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