您的位置:首页 > 大数据 > 人工智能

HDU 3376--Matrix Again【最大费用最大流 && 经典建图】

2015-08-28 11:37 567 查看

Matrix Again

Time Limit: 5000/2000 MS (Java/Others) Memory Limit: 102400/102400 K (Java/Others)

Total Submission(s): 3457 Accepted Submission(s): 1020



Problem Description
Starvae very like play a number game in the n*n Matrix. A positive integer number is put in each area of the Matrix.

Every time starvae should to do is that choose a detour which from the top left point to the bottom right point and than back to the top left point with the maximal values of sum integers that area of Matrix starvae choose. But from the top to the bottom can
only choose right and down, from the bottom to the top can only choose left and up. And starvae can not pass the same area of the Matrix except the start and end..

Do you know why call this problem as “Matrix Again”? AS it is like the problem 2686 of HDU.



Input
The input contains multiple test cases.

Each case first line given the integer n (2<=n<=600)

Then n lines, each line include n positive integers. (<100)



Output
For each test case output the maximal values starvae can get.


Sample Input
2
10 3
5 10
3
10 3 3
2 5 3
6 7 10
5
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9




Sample Output
28
46
80




题意:

给你一个N*N的矩阵,每个元素代表该处的权值。要求每个点只能走一次,左上角和右下角可以走两次但该处的权值只能获取一次。问你从左上角走到右下角(只能向下或右移动),再从右下角回到左上角(只能向上或左移动)所能得到的最大权值。

解析:

题目中问从左上角走到右下角,再从右下角回到左上角所能得到的最大权值,我们可以转化为从【左上角起点】到【右下角终点】走两次所获的最大权值。题目要求起点终点可以走多次,其他定只能走一次。按这种思路的话起点和终点的权值我们多算了一次,最后结果要减去。

把图看成 n * n个点,相互可到达的点之间建边, 可走次数作为边的容量,权值当做边的费用。问题就变成了最大费用最大流。

建图过程:

把每个每个点 i 拆成左点 i 和右点 i + n * n。虚拟一个超级源点 outset = 0, 超级汇点 inset = 2 * n * n + 1。

(1)当i 为起点或终点时,左点向右点建边,边的容量为2, 因为起点终点可以走两次, 边的费用为点的权值。

(若起点和终点拆成容量为1的边,那我们在起点的时候就只有一个选择:向上走或者向下走,在终点时只能选择是由上面的点传到终点还是由左边的点传入终点,这样的话就相当于求走一次的最大权值和,显然这样建图是不对的。)

(2)当i 不为起点或终点时,左点向右点建边,边的容量为1, 因为只能走一次,边的费用为点的权值。

(3)相互可到达的点之间建边,如 u --> v,建边时 右点u 和左点 v 建边,边的容量最小为1,边的费用为0。这点要想明白!!!

(4)源点向起点的左点建边,边的容量为2,边的费用为0,终点的右点向汇点建边,边的容量为2,费用为0。

这一类的题给你一个N*M的矩阵,对应N*M个点,且每个点都有一定的点权。当第一次到达某个位置时,我们可以获得该位置的点权且只能获取这一次。

这里可以分为两种情况:

(1)要求是每个点只能走一次(除了起点和终点可以走多次),问你从左上角到右下角走两次所获取的最大权值和。

(2)要求是每个点可以走多次,问你从左上角到右下角走K次所获取的最大权值和。

情况(1)应该都可以用这种方式建图。

情况(2)有POJ 3422题,POJ3422

建图步骤有什么不理解的地方,可以看这篇博客:小比的博客。里面还讲了情况(2)。(自己画了一上午图才想明白,早知道看小比的博客了,能省不少时间,有巨人的肩膀可以站就是好)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define INF 0x3f3f3f3f
#define maxn 800000 + 1000
#define maxm 4000000 + 1000
using namespace std;
int n;
int outset;
int inset;
struct node {
    int u, v, cap, flow, cost, next;
};

node edge[maxm];
int head[maxn], cnt;
int per[maxn];
int dist[maxn], vis[maxn];
int map[660][660];

void init(){
    cnt = 0;
    memset(head, -1, sizeof(head));
}

void add(int u, int v, int w, int c){
    node E1 = {u, v, w, 0, c, head[u]};
    edge[cnt] = E1;
    head[u] = cnt++;
    node E2 = {v, u, 0, 0, -c, head[v]};
    edge[cnt] = E2;
    head[v] = cnt++;
}

int change(int x, int y){
    return (x - 1) * n + y;
}

void getmap(){
    int t = n * n;
    outset = 0;
    inset = n * n * 2 + 1;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            scanf("%d", &map[i][j]);
            if(i == 1 && j == 1 || i == n && j == n)
                add(change(i, j), change(i, j) + t, 2, map[i][j]);
            else
                add(change(i, j), change(i, j) + t, 1, map[i][j]);
            if(i + 1 <= n)
                add(change(i, j) + t, change(i + 1, j), 1, 0);
            if(j + 1 <= n)
                add(change(i, j) + t, change(i, j + 1), 1, 0);
        }
    }
    add(outset, 1, 2, 0);
    add(change(n, n) + t, inset, 2, 0);
}

bool SPFA(int st, int ed){
    queue<int>q;
    for(int i = 0; i <= inset; ++i){
        dist[i] = -INF;
        vis[i] = 0;
        per[i] = -1;
    }
    dist[st] = 0;
    vis[st] = 1;
    q.push(st);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next){
            node E = edge[i];
            if(dist[E.v] < dist[u] + E.cost && E.cap > E.flow){
                dist[E.v] = dist[u] + E.cost;
                per[E.v] = i;
                if(!vis[E.v]){
                    vis[E.v] = 1;
                    q.push(E.v);
                }
            }
        }
    }
    return per[ed] != -1;
}

void MCMF(int st, int ed, int &cost, int &flow){
    flow = 0;
    cost = 0;
    while(SPFA(st, ed)){//每次寻找花销最小的路径
        int mins = INF;
        for(int i = per[ed]; i != -1; i = per[edge[i ^ 1].v]){
            mins = min(mins, edge[i].cap - edge[i].flow);
        }
         //增广
        for(int i = per[ed]; i != -1; i = per[edge[i ^ 1].v]){
            edge[i].flow += mins;
            edge[i ^ 1].flow -= mins;
            cost += edge[i].cost * mins;
        }
        flow += mins;
    }
}
int main (){
    while(scanf("%d", &n) != EOF){
        init();
        getmap();
        int cost, flow;
        MCMF(outset, inset, cost, flow);
        cost = cost - map[1][1] - map

;
        printf("%d\n", cost);
    }
    return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: