【练习试题】分组(poj1112 Team Them Up!)

【练习试题】分组(Poj1112 Team Them Up!)

Description

你的任务是把一些人分成两组,使得:
•每个人都被分到其中一组;
•每个组都至少有一个人;
•一组中的每个人都认识其他同组成员;
•两组的成员人数尽量接近。
这个问题可能有多个解决方案,你只要输出两组人数之差的绝对值即可,或者输出这样的分组法不存在。

Input

为了简单起见,所有的人都用一个整数标记,每个人号码不同,从1到N。
输入文件的第一行包括一个整数N(2≤N≤100),N就是需要分组的总人数;接下来的N行对应了这N个人,按每个人号码的升序排列,每一行给出了一串号码Aij (1≤Aij≤N,Aij≠i),代表了第i个人所认识的人的号码,号码之间用空格隔开,并以一个“0”结束。

Output

如果分组方法不存在,就输出信息“No solution”(输出时无需加引号)至输出文件;否则输出两组人数之差的绝对值。
接下来两行输出具体分组方案,每行第一个数位该组人数。

Sample Input

5
2 3 5 0
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0

Sample Output

1
2 2 4
3 1 3 5

Solution

我们发现,两个没有朋友关系的人必定会被分到两个不同的组合。也就是说,对于这张图,我们可以
理解为一张二分图。当且仅当双方都有朋友关系时,才进行连边。我们就可以将所有的点划分为一些
相对独立的块,每一块都有人数不同的两边,这两边必定是在不同的组合之中。
当然,如果说这里并不满足二分图的性质,那么也自然无法得出符合条件的解。
我们将每一个块当作一个物品,并且进行dp,就可以判定出最优方案的个数差了。然后根据我们dp的
过程在递归求出具体的解。 
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
	char c;int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9')rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n;
int map[105][105];
int c[105],cnt,flag;
int group[105][3],f[105][105];
int belong[505][105];
inline void Dfs(int v,int color,int p){
	if(c[v]){if(c[v]!=color)flag=1;return ;}
	c[v]=color;group[p][color]++;belong[p+(color-1)*n][group[p][color]]=v;
	for(int i=1;i<=n;i++){
		if(i==v||map[v][i])continue;
		Dfs(i,3-color,p);if(flag)break;
	}return ;
}
bool Judge(){
	for(int i=1;i<=n;i++){
		if(c[i])continue;
		Dfs(i,1,++cnt);
		if(flag)break;
	}return flag;
}
int vis[105];
inline void Print(int p,int v){
	if(f[p-1][v-group[p][1]]){
		for(int i=1;i<=group[p][1];i++){
			vis[belong[p][i]]=1;
		}Print(p-1,v-group[p][1]);
	}
	else if(f[p-1][v-group[p][2]]){
		for(int i=1;i<=group[p][2];i++){
			vis[belong[p+n][i]]=1;
		}Print(p-1,v-group[p][2]);
	}return ;
}
void Dp(){
	f[0][0]=1;
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<=n;j++){
			if(j>=group[i][1]&&f[i-1][j-group[i][1]])f[i][j]=1;
			if(j>=group[i][2]&&f[i-1][j-group[i][2]])f[i][j]=1;
		}
	}for(int i=n>>1;i>=0;i--)if(f[cnt][i]){cout<<n-2*i<<endl;Print(cnt,i);break;}
	return ;
}
int main(){
	n=read();int x;
	for(int i=1;i<=n;i++){
		while(1){
			x=read();if(x==0)break;
			map[i][x]=1;
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			if(map[i][j]==0||map[j][i]==0)
			    map[i][j]=map[j][i]=0;
	if(Judge())cout<<"No solution\n";
	else Dp();
	int tot=0;
	for(int i=1;i<=n;i++)tot+=vis[i];
	cout<<tot<<" ";
	for(int i=1;i<=n;i++)if(vis[i])cout<<i<<" ";
	cout<<endl<<n-tot<<" ";
	for(int i=1;i<=n;i++)if(vis[i]==0)cout<<i<<" ";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值