题意:有 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;
}