HDU 1569 最大点权独立集


 
   
最大点权独立集与最小点权覆盖是对偶问题,这里先介绍最小点权覆盖的解法。
最小点权覆盖问题是指,给出一张二分图,二分图的每个节点带有一个点权,要求从中选出若干节点,使得这些节点能够覆盖二分图中所有的边,并使得节点的权值和最小。
该类问题可用网络流最小割算法来解决。考虑最小割的性质,最小割能够将原图中所有的点划分为两个集合,这能够与最小点权覆盖问题中的点得选中与否对应;如果能够找到一种建模方式,满足在二分图中的每条边连接的两个点中,至少一个被选中,就能使网络流的最小割与二分图的最小点权覆盖相匹配。
于是考虑如下建模方式:建立源点s, 汇点t, 保留二分图中的所有节点xi,yi,从s到所有xi连边,权值为xi的权值;若二分图中xi到yi有边,在网络流图中从xi向yi连边,权值为无穷;从所有yi到t连边,权值为yi的权值。
这样,二分图中的每条边连接的两个点中,要么与s形成割边,要么与t形成割边(因为这两个点之间的边权为无穷),这就保证了两个点中,至少一个被选中,因此最小割就对应了一个最小点权的选点方案。故网络流图中的最小割就是原问题的最小点权。
而最大点权独立集问题中,需要满足在二分图中的每条边连接的两个点中,选择至多一个,最终是选择的点的点权和最大;换句话说,就是在二分图中的每条边连接的两个点中,至少 选择一个,使得 不选择的点的点权和最小。说到这里,大家应该能看出最大点权独立集合最小点权覆盖问题的联系了——最小点权覆盖的选点方案正好对应了最大点权独立集的 选点方案。
 
于是最大点权独立集的点权和=所有点权和-最小点权覆盖的点权。
 
下面给出一道例题 HDU 1569
 

方格取数(2)

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 4807    Accepted Submission(s): 1515
Problem Description
给你一个m*n的格子的棋盘,每个格子里面有一个非负数。 从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取数所在的2个格子不能相邻,并且取出的数的和最大。
 
Input
包括多个测试实例,每个测试实例包括2整数m,n和m*n个非负数(m<=50,n<=50)
 
Output
对于每个测试实例,输出可能取得的最大的和
 
Sample Input
 
     
3 3 75 15 21 75 15 28 34 70 5
 

Sample Output
 
     
188
 

Author
ailyanlu
 
该题的实质就是一个最大点权独立集问题。
首先可以看到一条性质,若将棋盘中的点抽象成图中的点,每个点向周围四个点连边,这样能够构成一张二分图。
其次,若按以上方式建图,该二分图的最大点权独立集的点权和便是最佳方案。
 
下面是代码:
#include 
#include 
#include 

using namespace std;

#define MAXN 3000
#define INF 1000000000

class Edge
{
    public:
        int u, v, w, next;
};
Edge edge[MAXN*8];

int fa[MAXN], head[MAXN], cur[MAXN], gap[MAXN], dis[MAXN], num;
/*
fa记录增广过程一个节点的父亲节点;
head是邻接表的表头;
cur当前弧优化数组;记录当前节点的第一条可能的允许弧;
gap是gap优化数组,gap[i]记录当前距离值为i的节点数;
dis储存节点的距离函数值
*/

void insert(int u, int v, int w)
{
    edge[num].u = u;
    edge[num].v = v;
    edge[num].w = w;
    edge[num].next = head[u];
    head[u] = num;

    num++;

    edge[num].u = v;
    edge[num].v = u;
    edge[num].w = 0;
    edge[num].next = head[v];
    head[v] = num;

    num++;
}
/*
最大流sap函数,传入参数s, t, n分别表示源节点,汇点,节点总数,函数返回网络流图中的最大流
复杂度O(E*V*V)

*/
int sap(int s, int t, int n)
{
        memset(fa, -1, sizeof(fa));
        memcpy(cur, head, sizeof(head));//初始时当前弧就是当前节点的第一条弧
        memset(dis, 0, sizeof(dis));//距离函数初始为0
        memset(gap, 0, sizeof(gap));

            gap[0] = n;//初始时距离为0的节点有n个

            int lowf = INF, top = s, flow = 0;//lowf是增广路上的最小流量,top是增广过程最前端的节点,flow待返回的流量

            while(dis[s] < n)//当汇点不可达时,dis[s]的值会被更改为节点总数n
            {
                bool flag = 0;//标记通过top节点能否找到允许弧

                for(int i = cur[top]; i != -1; i = edge[i].next)//从当前弧开始找允许弧
                {
                    int v = edge[i].v;
                    if(dis[top] == dis[v]+1 && edge[i].w > 0)//找到允许弧
                    {
                        flag = 1;//更改标记
                        cur[top] = i;//更改当前节点的当前弧,下次搜索时从这条弧开始
                        lowf = min(lowf, edge[i].w);//更新增广路上的流量
                        fa[v] = top;//记录父节点
                        top = v;//更改top节点

                        if(top == t)//如果找到终点,说明找到一条增广路,更新总流量
                        {
                            flow += lowf;

                            while(top != s)//沿父节点回溯更新残余网络
                            {
                                top = fa[top];

                                edge[cur[top]].w -= lowf;
                                edge[cur[top]^1].w += lowf;

                            }

                            lowf = INF;//重置最小流量
                        }
                        break;
                    }

                }

                /*
                如果没找到允许弧,撤退,更改当前top节点的距离值
                */
                int mindis;//更改top节点的距离值
                if(!flag)
                {
                    mindis = n;//初始化为节点总数

                    for(int i = head[top]; i != -1; i = edge[i].next)
                    {
                        if(edge[i].w > 0 && dis[edge[i].v] < mindis)//如果top节点能从距离比当前节点的距离更小的节点转移来
                        {
                                mindis = dis[edge[i].v];//更新top节点的距离值
                                cur[top] = i;//修改top节点的当前弧
                        }
                    }
                    if((--gap[dis[top]]) == 0) break;//gap优化,如果节点距离值出现断层,必然找不到增广路,直接退出
                    gap[dis[top] = mindis+1]++;//更新top节点的距离值以及gap数组

                    if(top != s) top = fa[top];//top撤退到它的父节点
                }

            }
            return flow;
}

int n, m;

int mat[55][55];
int dir[4][2] = { {1, 0}, {0, 1}, {0, -1}, {-1, 0} };

bool inbound(int row, int col)
{
    if(row < 0 || row >= n || col < 0 || col >= m) return 0;
    return 1;
}

int main()
{
    //freopen("in.txt", "r", stdin);

    while(scanf("%d%d", &n, &m) != -1)
    {
        int cnt = 1, src = 0, end = n*m+1, sum = 0;
        num = 0;
        memset(head, -1, sizeof(head));
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                scanf("%d", &mat[i][j]);
                sum += mat[i][j];
            }
        }

        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                if(((i+j)&1))
                {
                   // cout << mat[i][j] << " <<<<<" << endl;

                    insert(i*m+j+1, end, mat[i][j]);
                    continue;
                }

                insert(src, i*m+j+1, mat[i][j]);
               // cout << mat[i][j] << " >>>>>>>>> " << i*m+j+1 << endl;

                for(int k = 0; k < 4; k++)
                {
                    int r = i+dir[k][0], c = j + dir[k][1];

                    if(inbound(r, c))
                    {
                        insert(i*m+j+1, r*m+c+1, INF);
                    }

                }
            }
        }

        int ans = sap(src, end, n*m+2);
//cout << "ans " << ans << endl;

        printf("%d\n", sum-ans);

    }

    return 0;
}

你可能感兴趣的:(网络流&二分图)