题解【luogu2045 方格取数游戏加强版】

Description

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

Solution

一条边 \((a,b)\) 表示容量为 \(a\) ,费用为 \(b\)
把每个点拆成两个点,入点和出点。入点用来接受边,出点用来发出边
源点向 \((1,1)\) 连一条边 \((k,0)\)\((n,n)\) 向汇点连一条 \((k,0)\) ,表示可以走 \(k\)
每个点往他的右和下分别连一条 \((\infty, 0)\) 表示联通关系
每个点的入点与出点之间连两条边 \((1,x)\)\((\infty, 0)\)\(x\) 是该点的权值。
这是因为每个点只能取一次。
然后跑一遍最大费用最大流就完事啦
小技巧:把费用取负然后跑最小费用最大流

Code

#include <bits/stdc++.h>
using namespace std;
const int INF = 1000000000;
const int N = 550; 
int n, m, cnt, vis[N * N * 3], dis[N * N * 3]; 
int S, T, k, pre[N * N * 3], f[N * N * 3];  
struct node {
  int d, sid, tid; 
}a[N][N];
struct edge {
  int v, w, f; edge *next, *rev;
}pool[N * N * 2], *head[N * N * 3], *r[N * N * 3]; 
inline void addedge(int u, int v, int f, int w) {
  edge *p = &pool[++cnt], *q = &pool[++cnt]; 
  p->v = v, p->f = f, p->w = w,  p->next = head[u], head[u] = p; p->rev = q; 
  q->v = u, q->f = 0, q->w = -w, q->next = head[v], head[v] = q; q->rev = p;
}
inline bool spfa() {
  for(int i = S; i <= T; i++) r[i] = NULL, dis[i] = INF, vis[i] = 0, pre[i] = -1; 
  queue <int> Q; Q.push(S); vis[S] = 1; dis[S] = 0; f[S] = INF; 
  while(!Q.empty()) {
    int u = Q.front(); Q.pop(); vis[u] = 0; 
    for(edge *p = head[u]; p; p = p->next) {
      int v = p->v; 
      if(p->f > 0 && dis[v] > dis[u] + p->w) {
        dis[v] = dis[u] + p->w;
        pre[v] = u, r[v] = p; 
        f[v] = min(f[u], p->f); 
        if(!vis[v]) vis[v] = 1, Q.push(v); 
      }
    }
  } 
  return pre[T] != -1; 
}
inline int MCMF() {
  int ans = 0;  
  while(spfa()) {
    for(int i = T; i != S; i = pre[i]) {
      r[i]->f -= f[T]; r[i]->rev->f += f[T];  
    } ans += dis[T] * f[T];
  } return ans; 
}
int main() { 
  scanf("%d %d", &n, &k); S = 0, T = 2 * n * n + 1; 
  addedge(S, 1, k, 0); 
  for(int i = 1; i <= n; i++) {
    for(int j = 1; j <= n; j++) {
      int x; scanf("%d", &x); 
      int id = (i - 1) * n + j; 
      a[i][j].sid = 2 * id - 1;
      a[i][j].tid = 2 * id; 
      addedge(a[i][j].sid, a[i][j].tid, 1, -x); 
      addedge(a[i][j].sid, a[i][j].tid, INF, 0); 
    }
  } 
  for(int i = 1; i <= n; i++)
    for(int j = 1; j <= n; j++) {
      if(i < n) addedge(a[i][j].tid, a[i + 1][j].sid, INF, 0);
      if(j < n) addedge(a[i][j].tid, a[i][j + 1].sid, INF, 0); 
    }
  addedge(a[n][n].tid, T, k, 0); 
  printf("%d\n", -MCMF());
  return 0; 
}

转载于:https://www.cnblogs.com/acfunction/p/10105245.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值