好久没有动CSDN了,还好吗老友~
假期开始,博客也要跟进了~
蓝精灵的请求
题目大意:
在山的那边海的那边住着n个蓝精灵,这n个蓝精灵之间有m对好友关系,现在蓝精灵们想要玩一个团队竞技游戏,需要分为两组进行,且每一组中任意两个蓝精灵都是好友。另外,他们还想要最小化每组蓝精灵内部的好友关系数之和。蓝精灵们怎么都想不到如何分组来进行游戏,所以找到你来帮助他们分组。(若第一组内部的好友关系数为cnt1,第二组内部的好友关系数为cnt2,则“每组蓝精灵内部的好友关系数之和”为cnt1+cnt2)
题目思路:
首先,遇到分组问题,肯定要用图的补图的二分图判定。
为什么呢?做一个简单的证明:
边的含义就是 相邻顶点是朋友,我们建立补图,补图中只要两点没有边相连,说明两点是朋友,所以我们进行二分图染色,就可以把图染成两部分,每一部分的顶点之间互相之间没有任何的边相连,所以每一个顶点和所有顶点都为朋友关系。所以每一部分都是完全图,即就是答案中的结果。
所以
第一步:建立补图。
第二步:对补图进行二分图染色,将图中分成两部分,计算答案即可。
但是在第二步中,因为补图不一定是联通的,即补图可以分成两个或者多个子图,在每一个子图都是二分图的情况下,还是存在答案的,所以我们应该考虑子图的情况。
注意:每一个二分图子图中,两部分是不可以重合的,但是在任意两个二分图子图之间,是可以任意组合的,因为两个图间的任何顶点之间都没有边,即我们上述说的,都是朋友关系。
之后,从这n个组里,每组选出一个数,加起来可能的值为多少,遍历这个可能的值,因为一定为完全图,所以答案为:假设可以组成k个点,那么答案即为:k*(k-1)/2+(n-k)*(n-k-1)/2
所以,从这n个组里,每组选一个数,能组成什么值呢?这就是分组单一完全背包的问题。
设前i个背包组成k的状态,dp[i][k]==1表示 前i个背包可以组成答案为k的数
所以,状态转移方程即为:dp[i][k]=dp[i-1][k-w[j]] //w[j]为第i组第j个物品的值,注意每个物品只能使用一次。
代码中,用了滚动数组优化了一下空间复杂度,其实不优化也完全可以
AC:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e6+5;
const ll INF=1e13+7;
ll n,m,p;
vector<int>v[1005];
int mp[800][800];
int vis[1005];
int f[2][1005];
ll c0=0,c1=0;
bool dfs(int now,int c)
{
vis[now]=c;
if(c) ++c1;
else ++c0;
int sz=v[now].size();
for(int i=0;i<sz;i++)
{
int e=v[now][i];
if(vis[e]==c) return 0;
if(vis[e]==-1&&!dfs(e,c^1)) return 0;
}
return 1;
}
ll cal(ll x)
{
return x*(x-1)/2;
}
int main()
{
memset(vis,-1,sizeof(vis));
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
mp[x][y]=mp[y][x]=1;
}
for(int i=1;i<=n;i++)
for(int k=i+1;k<=n;k++)
if(!mp[i][k])
{
v[i].push_back(k);
v[k].push_back(i);
}
//建立补图
int now=0;
f[now][0]=1;
for(int i=1;i<=n;i++)
{
if(vis[i]==-1)
{
c0=c1=0;
int ff=dfs(i,1);
if(!ff)//有一个子图不是二分图
{
printf("-1\n");
return 0;
}
for(int k=0;k<=n;k++) f[now^1][k]=0;
for(int k=n;k>=c0;k--) f[now^1][k]|=f[now][k-c0];
for(int k=n;k>=c1;k--) f[now^1][k]|=f[now][k-c1];
now^=1;
}
}
ll res=INF;
for(int i=1;i<=n;i++)
if(f[now][i])
res=min(res,cal(i)+cal(n-i));
printf("%lld\n",res);
return 0;
}