[多校联考2019(Round 5 T2)]蓝精灵的请求(二分图染色+背包)

[多校联考2019(Round 5)]蓝精灵的请求(二分图染色+背包)

题面

在山的那边海的那边住着 n 个蓝精灵,这 n 个蓝精灵之间有 m 对好友关系,现在蓝精灵们想要玩一个团队竞技游戏,需要分为两组进行,且每一组中任意两个蓝精灵都是好友。另外,他们还想要最小化每组蓝精灵内部的好友关系数之和。蓝精灵们怎么都想不到如何分组来进行游戏,所以找到你来帮助他们分组。(若第一组内部的好友关系数为 cnt1,第二组内部的好友关系数为 cnt2,则“每组蓝精灵内部的好友关系数之和”为 cnt1+cnt2)

(n leq 700)

分析

实际上就是将一个图分成两个子图,使得每一个子图都是完全图。注意到没有边直接相连的点不能放在一个子图里。因此建原图的补图。那么补图中有边相连的点不能放在一个子图里。看到这个条件,我们想到了二分图染色。

于是我们对补图二分图染色。发现同一个联通块里相同颜色的点一定在同一个子图里。不同联通块里的两个点无论颜色都可以在一个子图里。

那么我们就可以预处理出(f[i])表示是否存在一种分组方式使得某组的蓝精灵数为(i).具体方法是对于每个联通块,我们求出两种颜色的个数(cnt[1],cnt[2]),那么类似背包问题,可以用(f[i])来更新(f[i+cnt[1]],f[i+cnt[2]]).注意要用滚动数组。

最后对于所有满足(f[i]=1)(i),求边数最小值即可

代码

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<algorithm> 
#define INF 0x3f3f3f3f
#define maxn 700 
using namespace std;
int n,m;
int g[maxn+5][maxn+5];
int invg[maxn+5][maxn+5];//反图
int col[maxn+5];
int cnt[5];
void dfs(int x,int c){
	col[x]=c;
	cnt[c]++;
	for(int y=1;y<=n;y++){
		if(y!=x&&g[x][y]==0){
			if(!col[y]) dfs(y,3-c);
			else if(col[y]==c){
				printf("-1
");
				exit(0);
			}
		}
	} 
} 

inline int get_e(int x){
	return x*(x-1)/2;
}

int f[2][maxn+5];
//类似背包,记录能不能凑出一组点数为i的子图 
int main(){
	int u,v;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&u,&v);
		g[u][v]=1;
		g[v][u]=1; 
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i!=j&&!g[i][j]) invg[i][j]=1;
		}
	}
	int now=1;
	f[1][0]=1;
	for(int i=1;i<=n;i++){
		if(!col[i]){
			cnt[1]=cnt[2]=0;
			dfs(i,1);
			for(int j=0;j<=n;j++){
				f[now^1][j+cnt[1]]|=f[now][j];
				f[now^1][j+cnt[2]]|=f[now][j];
			} 
			now^=1;
			//同一个二分图联通块里,不同颜色的点不能放一起 
		}
	}
	int ans=INF;
	for(int i=1;i<n;i++){
		if(f[now][i]) ans=min(ans,get_e(i)+get_e(n-i));
	}
	printf("%d
",ans);
}
原文地址:https://www.cnblogs.com/birchtree/p/11626514.html