题目大意:有n个人,把他们分成非空的两组,使得每个人都被分到一组,要求组内成员相互认识,并且两组人数差距最小。多解输出任意方案,无解输出No solution.
根据题意,如果有两个人不是互相认识,那么这两个人只能被分到不同的组。于是想到在不是相互认识的人之间连上一条边(注意一仅仅是一个人认识另一个人也需要连边),这样我们就得到了若干个连通分量。对于每个连通分量,相邻的两个点是必须分到两个组的,由此我们可以想到二分图染色。从连通分量任意一点出发,依次把点染色为0,1……对于一个点如果相邻点已经染色并且颜色和自己一样,则说明无解。对于每个连通分量,我们把它包含的点按照染上的颜色分为两组a,b,存到一个数组。
假如我们已经判断出了有tot个连通分量,并且设有两组A,B,那么对于每个连通分量都有两个决策:把a分到A组或者把a分到B组。有决策,有状态,我们很容易发现可以用动态规划求解,并且模型类似于01背包问题。
设f[i][j]表示讨论到第i个连通分量并且当前A组有j个人的状态是否能达到。转移方程如下:
f[i][j]=f[i-1][j-cnta](表示把a分到A组)
f[i][j]=f[i-1][j-cntb](表示把a分到B组)
之后就是使用相应的数据结构输出相应的解。
注意:下面贴出的代码并没有在virtual judge的uva 1627上AC。但是我认为没有写错……但是没有找到比较简单的数据反例。仅供参考。希望大神指正。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
using namespace std;
bool toget[105][105],havans=1,haveprint[105];
int t,n,tot,col[105];
struct Edge{
int from,to;
Edge(int from,int to):from(from),to(to){}
};
struct Divis{
int lcnt,rcnt;
vector<int>lob,rob;
void init(){
lcnt=rcnt=0;
lob.clear();
rob.clear();
}
}obj[105];
struct Situa{int val,ps;}f[105][105];
vector<Edge>edges;
vector<int>G[105];
void addedges(int from,int to){
edges.push_back(Edge(from,to));
G[from].push_back(edges.size()-1);
}
void clear(){
edges.clear();
for(int i=1;i<105;i++)G[i].clear();
memset(toget,0,sizeof(toget));
memset(col,-1,sizeof(col));
for(int i=1;i<105;i++)obj[i].init();
memset(f,0,sizeof(f));
tot=0;
havans=1;
memset(haveprint,0,sizeof(haveprint));
}
void dfs(int u,int d){
if(d){
obj[tot].lcnt++;
obj[tot].lob.push_back(u);
}
else {
obj[tot].rcnt++;
obj[tot].rob.push_back(u);
}
col[u]=d;
for(int i=0;i<G[u].size();i++){
Edge e=edges[G[u][i]];
if(col[e.to]>=0){
if(d==col[e.to])havans=0;
continue;
}
dfs(e.to,d^1);
}
}
int main(){
int v;
cin>>t;
while(t--){
clear();
cin>>n;
for(int i=1;i<=n;i++)
while(cin>>v&&v)toget[i][v]=1;
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
if(toget[i][j]==0||toget[j][i]==0){
addedges(i,j);
addedges(j,i);
}
for(int i=1;i<=n;i++)
if(col[i]==-1){
tot++;
dfs(i,1);
}
if(!havans){
puts("No solution");
continue;
}
f[0][0].val=1;
for(int i=1;i<=tot;i++){
for(int j=obj[i].lcnt;j<=n;j++)
if(f[i-1][j-obj[i].lcnt].val){
f[i][j].val=1;
f[i][j].ps=0;
}
for(int j=obj[i].rcnt;j<=n;j++)
if(f[i-1][j-obj[i].rcnt].val){
f[i][j].val=1;
f[i][j].ps=1;
}
}
int cur1=tot,cur2=0,ans=n,t2=0;
for(int i=min(obj[tot].lcnt,obj[tot].rcnt);i<=n;i++)
if(f[tot][i].val&&abs(2*i-n)<ans)ans=abs(2*i-n),cur2=i;
t2=n-cur2;
if((!t2)||(!cur2)){
puts("No solution");
continue;
}
printf("%d ",cur2);
stack<int>ansnotes;
while(true){
int temp;
ansnotes.push(f[cur1][cur2].ps);
if(cur1==1)break;
if(!f[cur1][cur2].ps)temp=obj[cur1].lcnt;
else temp=obj[cur1].rcnt;
cur1--,cur2-=temp;
}
while(!ansnotes.empty()){
int temp=ansnotes.top();
ansnotes.pop();
if(!temp)
for(int i=0;i<obj[cur1].lcnt;i++){
haveprint[obj[cur1].lob[i]]=1;
printf("%d ",obj[cur1].lob[i]);
}
else {
for(int i=0;i<obj[cur1].rcnt;i++){
haveprint[obj[cur1].rob[i]]=1;
printf("%d ",obj[cur1].rob[i]);
}
}
cur1++;
}
printf("\n%d ",t2);
for(int i=1;i<=n;i++)
if(!haveprint[i])printf("%d ",i);
putchar(10);
}
}
/*
2
5
3 4 5 0
1 3 5 0
2 1 4 5 0
2 3 5 0
1 2 3 4 0
5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0
*/