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

前言

我本来想着很快做对本题的,结果做了将近一个下午,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 0x7fffffff

using 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;
}

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值