hdu 1569 最小点权覆盖(最大流EK)

/*
题意:中文题

!!!与hdu1565一模两样!!!

题解:求最小权点覆盖集;
参考网站:http://yzmduncan.iteye.com/blog/1149057
最小权点覆盖=总权值-最大流
题目要求所选两个方格不能有共边,因此将所有的方格看作顶点,公共边看作连接两个顶点的边,这样
这个方形就转变为了一个无向图;题目要求两两顶点间没有公共边,即通过去除部分顶点来去除所有的
边,剩下的没有边的图即为没有公共边的所有方格,然后保证剩下的权值和最大,亦即求最小权点覆盖
集的权值之和;
建图方法:首先确定该图是一个二分图,然后在此基础上构建有向图,将二分图的两个集合x,y找出,
x和y之间的(u,v)权值变为INF,方向u->v;加入一个起点s和汇点t;s指向x的所有点,y的所有点指
向t,边的权值为该点的值。
然后求出最大流,用总和减去最大流即为最终解。

注意:在建图的时候反向边依旧需要添加。
*/
#include <iostream>
#include <cstring>
#include <queue>

#define EMAX 20050
#define VMAX 2555

using namespace std;

const int INF = 0x7fffffff;
int dir[4][2]={0, 1, 0, -1, 1, 0, -1, 0};

int EN;//边的总数

int head[VMAX];//用邻接表表示图
int pre[VMAX];//记录路径
int path[EMAX];//记录该点边的位置
struct edge
{
    int to;
    int weight;//流量
    int next;
}e[EMAX];

void insert(int u, int v, int w)
{
    e[EN].next = head[u];
    e[EN].to = v;
    e[EN].weight = w;
    head[u] = EN++;
    e[EN].next = head[v];
    e[EN].to = u;
    e[EN].weight = 0;
    head[v] = EN++;
}

int flow_ek(int s, int t)
{
    queue<int> Q;
    int ret = 0;
    while (true)
    {
        memset(pre,-1,sizeof(pre));
        while (!Q.empty())
            Q.pop();
        Q.push(s);
        while (!Q.empty())//BFS
        {
            int u = Q.front();
            Q.pop();
            for(int i=head[u]; i!=-1; i=e[i].next)
            {
                if (pre[e[i].to]==-1 && e[i].weight>0)
                {
                    pre[e[i].to] = u;//记录路径
                    path[e[i].to] = i;//记录该点边的所在位置,方便后面直接查询
                    Q.push(e[i].to);
                }
            }
            if (pre[t] != -1)//终点的pre不为-1,表示找到一条增广路径
                break;
        }
        if (pre[t] == -1)//当BFS后找不到增广路径则结束循环
            break;
        int mw = -1;
        for(int v=t; v!=s; v=pre[v])//找出当前路径中的流量的最小容量
        {
            if (mw == -1 || mw > e[path[v]].weight)
                mw = e[path[v]].weight;
        }
        for(int v=t; v!=s; v=pre[v])//修改路径的容量,求出残余网络
        {
            e[path[v]].weight -= mw;
            e[path[v]^1].weight += mw;//更新逆向边
        }
        ret += mw;
    }
    return ret;
}

int main(void)
{
    int n,m,temp;
    while (cin >> m >> n)
    {
        memset(head,-1,sizeof(head));
        EN = 0;
        int sum = 0;
        for(int i=0; i<m; i++)
        {
            for(int j=1; j<=n; j++)
            {
                cin >> temp;
                sum += temp;
                if ((i+j)%2 == 1)//插入于起点、汇点相连的边
                    insert(0,i*n+j,temp);
                else
                    insert(i*n+j,m*n+1,temp);
            }
        }
        for(int i=0; i<m; i++)
        {
            for(int j=1; j<=n; j++)
            {
                if ((i+j)%2 == 1)
                {
                    for(int k=0;k<4;k++)
                    {
                        int a=i+dir[k][0];
                        int b=j+dir[k][1];
                        if (a>=0&&a<m&&b>=1&&b<=n)//插入二分图之间的有向边
                        {
                            insert(i*n+j,a*n+b,INF);
                        }
                    }
                }
            }
        }
        cout << sum - flow_ek(0,m*n+1) << endl;
    }
    return 0;
}

 

posted @ 2014-03-20 23:45  辛力啤  阅读(336)  评论(0编辑  收藏  举报