您的位置:首页 > 其它

[HDU 5313] Bipartite Graph 二分图染色+分组背包

2015-07-26 21:48 302 查看
题意:输入一个二分图,通过加边使得这张图变成一个边数最多的完全二分图. 问最多能够新加多少条边. 注意重边是不允许的.

这题数据太弱了,导致好多只染一个联通块,把剩下的点都当成孤立点计算的代码都A过去了,然而题目题目可能出现多个联通块。

思路:首先二分图可以分成两类点X和Y, 完全二分图的边数就是X * Y.我们的目的是max {X * Y}, 并且X+ Y = n,所以只要X和Y的差最小,把原图黑白染色, 每个联通块有 ai 个黑点 bi 个白点, 于是就是要确定 X 应该选择 ai 还是 bi. 然后我们考虑dp, 因为每个联通块只能选择 ai 和 bi 中的一个, 所以想到分组背包,设ai, bi为物品 重量和价值都是n/2,背包容量也是n/2,这样就使得最优解接近n/2,这里和分组背包稍微有点不同,分组背包一个组合的物品可以不选,但是这里每个联通块里面我们必须选择一个,不过如果只是dp的话可能被某些数据卡死,比如 n = 10000, m = 0 , 解决这个问题我们只要加个剪枝,判断孤立点的个数足以使得X == Y or Y+1 or Y-1。

[code]#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

int n, m;
int sum1, sum2;
int dp[10005];
int vis[10005];
int sta[2][10005];

vector<int> mapn[10005];

void Init(int n)
{
    memset(vis, -1, sizeof(vis));
    for(int i = 0; i <= n; i++){
        mapn[i].clear();
    }
}

void Dfs(int rt) //染色
{
    if(vis[rt] == -1)
        vis[rt] = 0;
    int len = mapn[rt].size();
    for(int i = 0; i < len; i++){
        int v = mapn[rt][i];
        if(vis[v] == -1){
            vis[v] = (vis[rt] ^ 1);
            if(vis[v] == 0)
                sum1++; // 统计黑点
            else
                sum2++; // 统计白点
            Dfs(v);
        }
    }
}

int main()
{
    int Test;
    cin>>Test;
    while(Test--){
        cin>>n>>m;
        Init(n);
        int x, y;
        for(int i = 0; i < m; i++){
            cin>>x>>y;
            mapn[x].push_back(y);
            mapn[y].push_back(x);
        }
        int top = 0;
        int num = 0, num1 = 0, num2 = 0;
        for(int i = 1; i <= n; i++){
            if(vis[i] == -1){
                sum1 = 1;
                sum2 = 0;
                Dfs(i);
                sta[0][top] = sum1; //保存黑点
                sta[1][top++] = sum2; //保存白点
                if(sum2 == 0){
                    num++;  //孤立点
                }
                //粗略的填充完全二分图的两边,用于剪枝
                else if(num1 < num2){
                    num1 += sum1 > sum2 ? sum1 : sum2;
                    num2 += sum2 > sum1 ? sum1 : sum2;
                }
                else{
                    num2 += sum1 > sum2 ? sum1 : sum2;
                    num1 += sum2 > sum1 ? sum1 : sum2;
                }
            }
        }
        //剪枝
        if(abs(num1 - num2) <= num){
            cout<<((n+1)/2)*(n/2) - m<<endl;
            continue;
        }
        int minn = -1; // 保存选择i个联通块后最少的结点数。
        memset(dp, 0, sizeof(dp));
        for(int i = 0; i < top; i++){
            int minnn = 10000000000; //用于更新minn
            for(int j = n/2; j >= sta[0][i] || j >= sta[1][i]; j--){
                int ans = 0;
                //dp[j-sta[0][i]] >= minn 每个联通块都必须选
                if(j >= sta[0][i] && dp[j-sta[0][i]] >= minn && ans < dp[j-sta[0][i]] + sta[0][i]){
                    ans = dp[j-sta[0][i]] + sta[0][i];
                }
                if(j >= sta[1][i] && dp[j-sta[1][i]] >= minn && ans < dp[j-sta[1][i]] + sta[1][i]){
                    ans = dp[j-sta[1][i]] + sta[1][i];
                }
                dp[j] = ans;
                if(ans && ans < minnn) //更新
                    minnn = ans;
            }
            minn = minnn;  //更新
        }
        cout<<(dp[n/2] * (n - dp[n/2])) - m<<endl;
    }
    return 0;
}


欢迎大牛拍砖。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: