最小点权覆盖和最大点权独立集(定义与建模)

原文网址:http://yzmduncan.iteye.com/blog/1149057

二分图最小点覆盖和最大独立集都可以转化为最大匹配求解。在这个基础上,把每个点赋予一个非负的权值,这两个问题就转化为:二分图最小点权覆盖和二分图最大点权独立集。

 

    二分图最小点权覆盖

    从x或者y集合中选取一些点,使这些点覆盖所有的边,并且选出来的点的权值尽可能小。

建模:

    原二分图中的边(u,v)替换为容量为INF的有向边(u,v),设立源点s和汇点t,将s和x集合中的点相连,容量为该点的权值;将y中的点同t相连,容量为该点的权值。在新图上求最大流,最大流量即为最小点权覆盖的权值和。

 

二分图最大点权独立集

    在二分图中找到权值和最大的点集,使得它们之间两两没有边。其实它是最小点权覆盖的对偶问题。答案=总权值-最小点覆盖集。具体证明参考胡波涛的论文。



例:HDU1569

题意:一个m*n的棋盘,每个格子都有一个权值,从中取出某些数,使得任意两个数所在的格子没有公共边,并且所取去出的数和最大。求这个最大的值。

解:

    将格子染色成二分图,显然是求二分图的最大点权独立集。将问题转化为二分图最小点权覆盖来求解,最终结果=总权和-最大流。



/* 
最大点权独立集: 
转化为最小点权覆盖问题,最大点权独立集=总权值-最小点权覆盖集 
最小点权覆盖: 
设立源点s和t,s连边到点i,容量为i点的权值;点j连边到t,容量为j点权值;原二分图中的边容量为INF,求最大流即为最小点权覆盖。 
*/  
#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
const int INF = 0x7fffffff;  
const int maxv = 2600;  
const int maxe = 1000000;  
int n,m;  
int g[55][55];  
struct Edge  
{  
    int v;  
    int next;  
    int flow;  
};  
Edge e[maxe];  
int head[maxv],edgeNum;  
int now[maxv],d[maxv],vh[maxv],pre[maxv],preh[maxv];  
int start,end;  
  
void addEdge(int a,int b,int c)  
{  
    e[edgeNum].v = b;  
    e[edgeNum].flow = c;  
    e[edgeNum].next = head[a];  
    head[a] = edgeNum++;  
    e[edgeNum].v = a;  
    e[edgeNum].flow = 0;  
    e[edgeNum].next = head[b];  
    head[b] = edgeNum++;  
}  
  
void Init()  
{  
    edgeNum = 0;  
    memset(head,-1,sizeof(head));  
    memset(d,0,sizeof(d));  
}  
  
int sap(int s,int t,int n)       //源点,汇点,结点总数  
{  
    int i,x,y;  
    int f,ans = 0;  
    for(i = 0; i < n; i++)  
        now[i] = head[i];  
    vh[0] = n;  
    x = s;  
    while(d[s] < n)  
    {  
        for(i = now[x]; i != -1; i = e[i].next)  
            if(e[i].flow > 0 && d[y=e[i].v] + 1 == d[x])  
                break;  
            if(i != -1)  
            {  
                now[x] = preh[y] = i;  
                pre[y] = x;  
                if((x=y) == t)  
                {  
                    for(f = INF,i=t; i != s; i = pre[i])  
                        if(e[preh[i]].flow < f)  
                            f = e[preh[i]].flow;  
                    ans += f;  
                    do  
                    {  
                        e[preh[x]].flow -= f;  
                        e[preh[x]^1].flow += f;  
                        x = pre[x];  
                    }while(x!=s);  
                }  
            }  
            else  
            {  
                if(!--vh[d[x]])  
                    break;  
                d[x] = n;  
                for(i=now[x]=head[x]; i != -1; i = e[i].next)  
                {  
                    if(e[i].flow > 0 && d[x] > d[e[i].v] + 1)  
                    {  
                        now[x] = i;  
                        d[x] = d[e[i].v] + 1;  
                    }  
                }  
                ++vh[d[x]];  
                if(x != s)  
                    x = pre[x];  
            }  
    }  
    return ans;  
}  
  
  
void build()  
{  
    int i,j;  
    for(i = 1; i <= m; i++)  
    {  
        for(j = 1; j <= n; j++)  
        {  
            int t = (i-1)*n+j;  
            if((i+j)%2)  
            {  
                addEdge(start,t,g[i][j]);  
                if(i>1)  
                    addEdge(t,t-n,INF);  
                if(i<m)  
                    addEdge(t,t+n,INF);  
                if(j>1)  
                    addEdge(t,t-1,INF);  
                if(j<n)  
                    addEdge(t,t+1,INF);  
            }  
            else  
                addEdge(t,end,g[i][j]);  
        }  
    }  
}  
  
int main()  
{  
    int i,j;  
    int result;  
    while(scanf("%d %d",&m,&n) != EOF)  
    {  
        result = 0;  
        Init();  
        for(i = 1; i <= m; i++)  
        {  
            for(j = 1; j <= n; j++)  
            {  
                scanf("%d",&g[i][j]);  
                result += g[i][j];  
            }  
        }  
        start = 0;  
        end = n*m + 1;  
        build();  
        printf("%d\n",result-sap(start,end,end+1));  
    }  
    return 0;  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值