2020-11-05NOIP模拟T1【二分染色】【DP/01背包】

思路

原题链接(考试题不要求输出方案)
第一步:建反图
这道题如果想到了建反图思路就很明显了。
建反图:如果两个人不互相认识,就连一条边。对反图求出所有连通块。容易发现在反图里连通的点一定不是相互认识的且在反图中不同连通分量中的点一定是相互认识的,那么就是说如果在反图里有边那么在原图里一定不能在同一个集合中。对相邻的顶点进行二分图染色,如果相邻点可以被染成不同颜色,那么就可行,反之不行。那么这道题就是在反图上进行二分图染色判断。
第二步: 01 01 01背包
考虑这个染色问题的附加限制条件:要使两个集合的人数尽可能相等。
找出反图里所有的连通分支,每个连通分支分为两个集合,如果有k个连通分支,那么就有 2 ∗ k 2*k 2k个集合,现在的问题就是把划分出来的 2 ∗ k 2*k 2k个集合分成两组,使其数量尽可能相等。相当于有 2 ∗ k 2*k 2k件物品,要装进一个背包,使背包中的人尽可能接近总人数的一半。
d p [ i ] [ j ] ( 0 < = i , j < = 100 ) dp[i][j](0<=i,j<=100) dp[i][j](0<=i,j<=100)表示第 1 1 1组为 i i i个人,第 2 2 2组为 j j j个人这个状态; d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1表示存在。初始 d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 dp[0][0]=1;然后对每个连通分支 D P DP DP,然后套 01 01 01背包判断当前决策是否可行,最后取个差值最小的。

考场

在考场上想到了二分图判定,但是没有想到建反图,于是考挂了 ⋯ \cdots
正难则反
如果对于原来的图不可行的话考虑它的反图有什么特殊性质然后分析看看能不能找到解法。然后遇到选择方案数一般可以考虑 D P DP DP,不要怕 D P DP DP,一般好好想想,看看有没有类似模型或者看怎么转移,都可以设计出方程。

代码

随便写写竟然在团队里跑了个最优解系列 . j p g .jpg .jpg

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<1)+(cnt<<3)+(c^48);c=getchar();}
	return cnt*f;
}
int n,vis[N],c[N],sz[N][2],cnt;
bool a[N][N],f[N][N],mp[N][N];
bool dfs(int s,int nowc){
	if(vis[s]) return c[s]==nowc;
	vis[s]=true;c[s]=nowc;++sz[cnt][nowc];
	for(int i=1;i<=n;++i)
		if(a[s][i]&&!dfs(i,!nowc)) return false;
	return true;
}
bool getpart(){
	for(int i=1;i<=n;++i)
		if(!vis[i]){
			++cnt;if(!dfs(i,0)) return false;
		}
	return true;
}
inline void work(){
	for(int i=0;i<=n;++i)
		for(register int j=0;j<=n;++j)
			f[i][j]=0;
	f[0][0]=1;
	for(int i=1;i<=cnt;++i)
		for(register int j=n;j>=0;--j){
			if(j>=sz[i][0]&&f[i-1][j-sz[i][0]]) f[i][j]=1;
			if(j>=sz[i][1]&&f[i-1][j-sz[i][1]]) f[i][j]=1;
		}
	int ans=n;
	for(int i=0;i<=n;++i)
		if(f[cnt][i]) ans=min(ans,abs(n-i-i));
	if(ans>=n) printf("No solution\n");
	else printf("%d\n",ans);
}
signed main(){
	int T=read();
	while(T--){
		n=read();cnt=0;
		for(int i=1;i<=n;++i) vis[i]=c[i]=sz[i][0]=sz[i][1]=0;
		for(int i=1;i<=n;++i)
			for(register int j=1;j<=n;++j)
				a[i][j]=read(),a[i][j]=!a[i][j];
		for(int i=1;i<=n;++i)
			for(register int j=1;j<i;++j)
				if(a[i][j]||a[j][i])
					a[i][j]=a[j][i]=true;
		if(getpart()) work();
		else printf("No solution\n");
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值