大联欢 party.cpp

33 篇文章 0 订阅
24 篇文章 1 订阅

【一句话】有n 个点m条无向边,问是否能使n个点划分到两个集合中使得每个集合都是一个完全图。如果不可以输出-1,如果可以则输出两个集合最小的边数总和。

【分析】可以说是图论的经典一题。正方向建图似乎很难考虑,那我们考虑建反图(有边的没边,没边的有边)。反图中每条边表示的意义就是,两个点不能在同一个集合(如果在同一个集合,则集合一定不是完全图)。
再者进行二分图染色,如果我不存在矛盾的情况,就一定会可以将这些点分配到两个集合中。显然最小边数就是一个二次函数,所以找最接近n/2的组合就可以了。显然可以背包完成。

【code】

#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=710;
int n,m;
struct Edge{
	int v,nxt;
}edge[maxn*maxn];
int head[maxn],tot;
int v[maxn][maxn];
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9') tmp=getchar();
	while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline void add_edge(int x,int y){
	edge[tot].v=y;
	edge[tot].nxt=head[x];
	head[x]=tot++;
}
int clr[maxn],cnt_1=0,cnt=0;
bool dfs(int u,int d){
	cnt++,cnt_1+=d;
	clr[u]=d;
	for(int i=head[u];i!=-1;i=edge[i].nxt){
		int v=edge[i].v;
		if(clr[v]==-1){
			if(!dfs(v,d^1)) return false;
		}
		else if(clr[v]!=d^1) return false;
	}
	return true;
}
bitset<1000>b;
int main(){
	freopen("party.in","r",stdin);
	freopen("party.out","w",stdout);
	memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int p,q;read(p),read(q);
		v[p][q]=v[q][p]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(!v[i][j]&&i!=j) add_edge(i,j);//anti-edge
	memset(clr,-1,sizeof(clr));
	b[0]=1;
	for(int i=1;i<=n;i++)
		if(clr[i]==-1){
			cnt=0,cnt_1=0;
			if(!dfs(i,1)){
				printf("-1\n");
				return 0;
			}
			cnt-=cnt_1;
			b=(b<<cnt)|(b<<cnt_1);//简洁优美快速的bitset
		}
	for(int i=n>>1;i>=0;i--)
	if(b[i]){
		printf("%d\n",i*(i-1)/2+(n-i)*(n-i-1)/2);
		break;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值