方格取数(2) —(网络流)

版权声明:本文为博主原创文章,转载请说明出处。 https://blog.csdn.net/xianpingping/article/details/79274579

题目:

给你一个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

思路:

本题要充分发挥想象,建模是关键。

将棋盘分为黑白棋子,(黑与白呈格子状)建立超级源点s,假设坐标(1,1)为代表的是白棋。白棋与超级源点相连,flow为该点的值。黑棋与超级汇点相连,flow为该点的值。将白棋与上下左右相连,流量为inf,跑一遍最大流,用所有的权值减去最大流即为答案。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,np,nc,m;
char x;
int point,point2,point3;
const int maxE = 200000;
const int maxN = 8000;
const int maxQ = 200000;
const int oo = 1e+9;
const int inf=0x3f3f3f3f;

struct Edge {
   int v;//弧尾
   int c;//容量
   int n;//指向下一条从同一个弧头出发的弧
} edge[maxE];//边组


int adj[maxN], cntE;//前向星的表头
int Q[maxQ], head, tail;//队列
int d[maxN], cur[maxN], pre[maxN], num[maxN];
int source, sink, nv;//sourse:源点,sink:汇点,nv:编号修改的上限


void add(int u, int v, int c) {//添加边
    //正向边
    edge[cntE].v = v;
    edge[cntE].c = c;//正向弧的容量为c
    edge[cntE].n = adj[u];
    adj[u] = cntE++;

    //反向边
    edge[cntE].v = u;
    edge[cntE].c = 0;//反向弧的容量为0
    edge[cntE].n = adj[v];
    adj[v] = cntE++;
}

void rev_bfs () {//反向BFS标号
    memset(num,0,sizeof(num));
    memset(d,-1,sizeof(d));//没标过号则为-1

    d[sink] = 0;//汇点默认为标过号
    num[0] = 1;
    head = tail = 0;
    Q[tail++] = sink;

    while (head != tail) {
        int u = Q[head++];
        for (int i = adj[u]; ~i; i = edge[i].n) {
            int v = edge[i].v;
            if (~d[v]) continue;//已经标过号
            d[v] = d[u] + 1;//标号
            Q[tail++] = v;
            num[d[v]]++;
        }
    }
}

int ISAP() {
    //copy (cur, adj);//复制,当前弧优化
    memcpy(cur,adj,sizeof(cur));
    rev_bfs ();//只用标号一次就够了,重标号在ISAP主函数中进行就行了
    int flow = 0, u = pre[source] = source, i;

    while (d[sink] < nv) {//最长也就是一条链,其中最大的标号只会是nv - 1,如果大于等于nv了说明中间已经断层了。
        if (u == sink) {//如果已经找到了一条增广路,则沿着增广路修改流量
            int f = oo, neck;
            for (i = source; i != sink; i = edge[cur[i]].v) {
                if (f > edge[cur[i]].c){
                    f = edge[cur[i]].c;//不断更新需要减少的流量
                    neck = i;//记录回退点,目的是为了不用再回到起点重新找
                }
            }
            for (i = source; i != sink; i = edge[cur[i]].v) {//修改流量
                edge[cur[i]].c -= f;
                edge[cur[i] ^ 1].c += f;
            }
            flow += f;//更新
            u = neck;//回退
        }
        for (i = cur[u]; ~i; i = edge[i].n) if (d[edge[i].v] + 1 == d[u] && edge[i].c) break;
        if (~i) {//如果存在可行增广路,更新
            cur[u] = i;//修改当前弧
            pre[edge[i].v] = u;
            u = edge[i].v;
        }
        else {//否则回退,重新找增广路
            if (0 == (--num[d[u]])) break;//GAP间隙优化,如果出现断层,可以知道一定不会再有增广路了
            int mind = nv;
            for (i = adj[u]; ~i; i = edge[i].n) {
                if (edge[i].c && mind > d[edge[i].v]) {//寻找可以增广的最小标号
                    cur[u] = i;//修改当前弧
                    mind = d[edge[i].v];
                }
            }
            d[u] = mind + 1;
            num[d[u]]++;
            u = pre[u];//回退
        }
    }

    return flow;
}

void init () {//初始化
    memset(adj,-1,sizeof(adj));
    cntE = 0;
}
int main()
{
    int n,m;
    int summ;
    int a[55][55];
    while(cin>>n>>m)
    {
        init();
        source=0;
        sink=n*m+1;
        nv=sink+1;
        summ=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                cin>>a[i][j];
                summ+=a[i][j];
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if((i+j)%2==0)
                {
                    add(source,(i-1)*m+j,a[i][j]);
                if(i<n) add((i-1)*m+j,(i-1)*m+j+m,inf);
                if(i>1) add((i-1)*m+j,(i-1)*m+j-m,inf);
                if(j<m) add((i-1)*m+j,(i-1)*m+j+1,inf);
                if(j>1) add((i-1)*m+j,(i-1)*m+j-1,inf);
                }
                else
                    add((i-1)*m+j,sink,a[i][j]);

            }
        }
        cout<<summ-ISAP()<<endl;
    }
    return 0;
}


展开阅读全文

没有更多推荐了,返回首页