【一句话】有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;
}