题意:有 n 个人,他们之间可能相互认识。现在想把这些人分成两组,每个组里面所有人都相互认识,如果可以分成这两组,找出两组人数相差最少的情况。
思路:如果将n个人之间不是相互认识的连一条边,那么如果整个图能够表示为二部图则必有解,否则无解。有解得情况下,用背包判断即可。题目需要记录的东西比较多,所以开的数组和流程都比较繁琐。
其中的连通分支我用一个类似临界表的方式进行存储。每一个连通分支内染两种相邻的颜色。
#include <stdio.h> #include <string.h> #define clr(x,d) memset(x,d,sizeof(x)) #define N 105 int first[N<<1],next[N<<1],num[N<<1],last[2],com = 0,res1[N],res2[N],top1,top2; int dp[N][N],g[N][N],n,flag[N]; void init(){ top1 = top2 = 0; clr(first,-1); clr(num,0); clr(dp,0); clr(g,0); clr(next,-1); clr(flag, -1); } int test_bipartite(int x,int color){ int y,res=1; flag[x] = color; num[color]++; if(first[color]==-1) first[color] = x; next[last[color&1]] = x; last[color&1] = x; for(y = 1;y<=n;y++) if(g[x][y]){ if(flag[y]==color) return 0; else if(flag[y]==-1) res &= test_bipartite(y,color^1); } return res; } void print(int c,int id){ int i; if(id==1) for(i = first[c];i!=-1;i=next[i]) res1[top1++] = i; else for(i = first[c];i!=-1;i=next[i]) res2[top2++] = i; } int main(){ int i,j; init(); scanf("%d",&n); for(i = 1;i<=n;i++) while(scanf("%d",&j) && j) g[i][j] = 1; for(i = 1;i<n;i++) for(j = i+1;j<=n;j++) g[i][j]=g[j][i] = (g[i][j]+g[j][i])==2?0:1; for(i = 1;i<=n;i++) if(-1 == flag[i]){ last[0] = last[1] = 0; if(!test_bipartite(i,com)) break; com += 2; } if(i<=n) printf("No solution\n"); else{ dp[0][0] = 1; for(i = 1;i<=com/2;i++) for(j = 0;j<=n/2;j++) if(dp[i-1][j]){ if(j+num[2*i-2]<=n/2) dp[i][j+num[2*i-2]] = 1; if(j+num[2*i-1]<=n/2) dp[i][j+num[2*i-1]] = 2; } for(i = n/2;i>=0;i--) if(dp[com/2][i]) break; for(j = com/2;j>0;j--){ if(dp[j][i] == 1){ print(2*j-2,1); print(2*j-1,2); i -= num[2*j-2]; }else{ print(2*j-1,1); print(2*j-2,2); i -= num[2*j-1]; } } printf("%d",top1); for(i = 0;i<top1;i++) printf(" %d",res1[i]); printf("\n"); printf("%d",top2); for(i = 0;i<top2;i++) printf(" %d",res2[i]); printf("\n"); } return 0; }