洛谷P1285 队员分组

题目

有 nn 个人从 11 至 nn 编号,相互之间有一些认识关系,你的任务是把一些人分成两组,使得:

  • 每个人都被分到其中一组。
  • 每个组都至少有一个人。
  • 一组中的每个人都认识其他同组成员。

在满足上述条件的基础上,要求两组成员的人数之差(绝对值)尽可能小。请构造一种可行的方案。

请注意,xx 认识 yy 不一定说明 yy 认识 xx;xx 认识 yy 且 yy 认识 zz 不一定说明 xx 认识 zz。即认识关系是单向且不可传递的。

输入格式

输入的第一行是一个整数,代表总人数 nn。

第 22 到第 (n + 1)(n+1) 行,每行有若干个互不相同的整数,以 00 结尾,第 (i + 1)(i+1) 行的第 jj 个整数 a_{i, j}ai,j​(00 除外)代表第 ii 个人认识 a_{i, j}ai,j​。

输出格式

本题存在 Special Judge

如果无解,请输出一行一个字符串 No solution

如果有解,请输出两行整数,分别代表两组的成员。每行的第一个整数是该组的人数,后面以升序若干个整数代表该组的成员编号,数字间用空格隔开。

输入输出样例

输入 #1                   输出 #1
5                        3 1 3 5
2 3 5 0                 2 2 4
1 4 5 3 0
1 2 5 0
1 2 3 0
4 3 2 1 0


二分图染色 + DP

对于一个队员,如果和另一个不是双向的,那这两个一定不能在同一组中。

最终分为的两组我们用 AA 和 BB 表示。

每个点向那些和该点一定不同组的点连边。我们从一个点出发,不断用两种颜色间隔染色,直到不能再处理为止。所有处理过的点为一个连通块,而连通块内有两组不同颜色的点,互相冲突。由于每个连通块只能选其中一组进入组 AA ,另一组进入组 BB ,在只考虑组 AA 选哪组的情况下,我们用 DP 来解决剩下的问题。

状态表示: f[i][j]f[i][j] 表示组 AA 在前 ii 个连通块中选,能不能达到人数为 jj。能,则 f[i][j]=1f[i][j]=1 ,反之为 00 。

状态转移:对于连通块 ii ,选颜色为 11 还是颜色为 22 的组分别对应两种方案,剩下的很好想了。

认真看看代码就懂了QAQ

#include <bits/stdc++.h>
using namespace std;
const int MN=105;
const int MM=105;
typedef long long ll;

bool g[MN][MN],f[MN][MN];
int n,num[MM][2],pre[MN][MN],ans;
vector<int> cont[MN][2];

int mtc[MN],sum=0;
void dfs(int x,int fa,int col)
{
	num[sum][mtc[x]=col]++;cont[sum][col].push_back(x);
	for (int i=1;i<=n;++i)
		if (!g[i][x] && i!=fa && x!=i)
		{
			if (mtc[i]==-1) dfs(i,x,col^1);
			else if (mtc[i]==col) {printf("No solution");exit(0);}
		}
}
void dp()
{
	f[0][0]=true; //as a beginner.
	for (int i=1;i<=sum;++i)
		for (int j=0;j<=n/2;++j)
		{
			int t=j-num[i][0];
			//what can be carried must be the best one.
			if (t>=0&&f[i-1][t]) f[i][j]=true,pre[i][j]=0;
			t=j-num[i][1];
			if (t>=0&&f[i-1][t]) f[i][j]=true,pre[i][j]=1;
		}
	for (int i=n/2;i>=0;--i) if (f[sum][i]) {ans=i;break;}
}
void print()
{
	int res[MN]={0},t=ans;
	bool p[MN]={false};
	for (int i=sum;i>0;--i)
	{
		for (int j=0;j<cont[i][pre[i][t]].size();++j) p[res[++res[0]]=cont[i][pre[i][t]][j]]=true;
		t-=num[i][pre[i][t]];
	}
	sort(res+1,res+res[0]+1);
	printf("%d ",res[0]);for (int i=1;i<=res[0];++i) printf("%d ",res[i]);
	printf("\n%d ",n-res[0]);for (int i=1;i<=n;++i) if (!p[i]) printf("%d ",i);
}
ll read(){
	ll f=1,x=0;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c<='9'&&c>='0'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int main()
{
	n=read();
	int m,v;
	for (int i=1;i<=n;++i)
	{
		v=-1;
		while (v!=0){v=read();g[i][v]=true;}
	}
	for (int i=1;i<=n;++i)
		for (int j=i+1;j<=n;++j)
		{
			if (!(g[i][j]&&g[j][i]) && (g[i][j]||g[j][i])) g[i][j]=g[j][i]=false;
		}
	memset(mtc,-1,sizeof(mtc));
	for (int i=1;i<=n;++i)
		if (mtc[i]==-1)
		{
			sum++;
			dfs(i,0,1);
		}
	dp();
	print();
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值