NOI 2019 多校联合 蓝精灵的请求 二分图匹配 分组背包

好久没有动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;
}

发布了157 篇原创文章 · 获赞 146 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/103940672