洛谷 P2045 方格取数加强版 (费用流)

来源:互联网 发布:ubuntu清空文件夹 编辑:程序博客网 时间:2024/06/09 21:05

前言

我本来想着很快做对本题的,结果做了将近一个下午,WA了四次,才换来难得的AC,看看还是有总结的必要,从中吸取教训。

这里写图片描述


题目描述

给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= 1000)现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要求K次所达到的方格的数的和最大


输入输出格式

输入格式:

第一行两个数n,k(1<=n<=50, 0<=k<=10)

接下来n行,每行n个数,分别表示矩阵的每个格子的数

输出格式:

一个数,为最大和


输入输出样例

输入样例#1:

3 1
1 2 3
0 2 1
1 4 2

输出样例#1:

11


说明

每个格子中的数不超过100


分析

这题要求一共走k次,即每个格子最多走k次,每个格子的价值最多获得一次。像这种限制点或边使用次数的题,我们往往将其转化为限制网络中边的流量(点要拆分)来解。纵观题目,n不到50,用网络流的信念应该更加坚定。然而使得到的价值最大,肯定是用最小费用最大流的变体——“最大费用最大流”来求解。凡是在图上走以求得到最大值的题,一般可以考虑dp或”最长路径”之类的东西,但边取的次数(也可能是点)有限制,就可以排除最短路,转而转化成费用流的问题了。

网络流的题难在难以看出来它是网络流。

费用流的模板一套,我们很快搞出最大费用最大流来。但构图才是网络流的最大的难点。像这种点的流量有限制的题,必然要拆点,然而我这个菜鸡居然naive的认为不拆点也可以,于是在我几次WA后居然改成了不拆点的构图=。=,直到最后才恍然大悟,真是too young。

我们将每个点拆成入点和出点,入点向出点连两条边:一条容量为1,花费为格子的价值,另一条容量为k-1,花费为0。然后按题目构图,从一个点的出边连向另一个点的入边,容量为k,费用为0。最后构出source和sink,向左上和右下连边,容量为k,费用为0。最后跑最大费用流就行了。

其实讲真挺简单,但是不注重细节带来的就是难忘的WA。


代码:

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <cmath>#include <queue>#include <algorithm>#define N 60#define INF 0x7fffffffusing namespace std;queue <int> q;bool vis[N*N*2];int n, k, cur = -1, head_p[N*N*2], val[N][N];int s, t, flow[N*N*2], pre_e[N*N*2], pre_v[N*N*2], dis[N*N*2];struct Tadj{    int next, obj, cap, cost;    Tadj() {}    Tadj(int next, int obj, int cap, int cost):      next(next), obj(obj), cap(cap), cost(cost) {}}Edg[N*N*16];void Insert(int a, int b, int c, int d){    cur ++;    Edg[cur] = Tadj(head_p[a], b, c, d);    head_p[a] = cur;}bool Spfa(){    for(int i = s; i <= t; i++)  dis[i] = -INF, vis[i] = false;    q.push(s);    dis[s] = 0;    vis[s] = true;    flow[s] = INF;    while(!q.empty()){      int now = q.front();  q.pop();      for(int i = head_p[now]; ~ i; i = Edg[i].next){        int v = Edg[i].obj, c = Edg[i].cap, l = Edg[i].cost;        if(!c)  continue;        if(dis[v] < dis[now] + l){          dis[v] = dis[now] + l;          flow[v] = min(flow[now], c);          pre_v[v] = now;          pre_e[v] = i;          if(!vis[v]){            q.push(v);            vis[v] = true;          }        }      }      vis[now] = false;    }    return dis[t] != -INF;}int Max_cost_Max_flow(){    int cost = 0;    while(Spfa()){      cost += dis[t] * flow[t];      for(int i = t; i != s; i = pre_v[i]){        Edg[pre_e[i]].cap -= flow[t];        Edg[pre_e[i]^1].cap += flow[t];      }    }    return cost;}int main(){    scanf("%d%d", &n, &k);    for(int i = 1; i <= n; i++)     for(int j = 1; j <= n; j++)       scanf("%d", &val[i][j]);    s = 0;    t = n * n * 2 + 1;    for(int i = s; i <= t; i++)  head_p[i] = -1;    Insert(s, 1, k, 0);    Insert(1, s, 0, 0);    Insert(n*n*2, t, k, 0);    Insert(t, n*n*2, 0, 0);    for(int i = 1; i <= n; i++)     for(int j = 1; j <= n; j++){       Insert((i-1)*n+j, (i-1)*n+j+n*n, 1, val[i][j]);       Insert((i-1)*n+j+n*n, (i-1)*n+j, 0, -val[i][j]);       Insert((i-1)*n+j, (i-1)*n+j+n*n, k-1, 0);       Insert((i-1)*n+j+n*n, (i-1)*n+j, 0, 0);     }      for(int i = 1; i <= n; i++)     for(int j = 1; j <= n; j++){       if(i < n){         Insert((i-1)*n+j+n*n, i*n+j, k, 0);         Insert(i*n+j, (i-1)*n+j+n*n, 0, 0);       }          if(j < n){         Insert((i-1)*n+j+n*n, (i-1)*n+j+1, k, 0);         Insert((i-1)*n+j+1, (i-1)*n+j+n*n, 0, 0);       }     }    printf("%d\n", Max_cost_Max_flow());    return 0;}

既然目标是地平线,留给世界的只能是背影。

0 0
原创粉丝点击