POJ 3422 最大费用流

题意

传送门 POJ 3422 Kaka’s Matrix Travels

题解

此类点或边带有权值和上界限制的最优化问题,恰好与费用流的模型非常相似。

使用点边转化的技巧,将格子 ( i , j ) (i,j) (i,j) 代表的节点拆为“入点” x x x 与“出点” x ′ x' x。从每个 ( i , j ) (i,j) (i,j) x x x x ′ x' x 连一条容量为 1 1 1 费用为 m a t r i x [ i ] [ j ] matrix[i][j] matrix[i][j] 的边,以及一条容量为 k − 1 k-1 k1 费用为 0 0 0 的边,代表第一次经过这个点时可以把数取走,之后不再计算;每个节点的“出点”向网格中可以转移(向下或向右)点的“入点”连一条容量为 k k k 费用为 0 0 0 的边。以 ( 0 , 0 ) (0,0) (0,0) 的“入点”为源点, ( n − 1 , n − 1 ) (n-1,n-1) (n1,n1) 的“出点”为汇点求解最大费用流即可。总时间复杂度 O ( K N 4 ) O(KN^4) O(KN4)

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 55, maxv = 2 * maxn * maxn, maxe = 8 * maxn * maxn;
int N, K, num[maxn][maxn], id[maxn][maxn][2];
int tot, head[maxv], to[maxe], cap[maxe], cost[maxe], nxt[maxe];
int ds[maxv], rst[maxv], pre[maxv];
bool inq[maxv];

inline void add(int x, int y, int c, int z)
{
    to[++tot] = y, cap[tot] = c, cost[tot] = z, nxt[tot] = head[x], head[x] = tot;
    to[++tot] = x, cap[tot] = 0, cost[tot] = -z, nxt[tot] = head[y], head[y] = tot;
}

bool spfa(int s, int t)
{
    memset(ds, 0xcf, sizeof(ds));
    memset(inq, 0, sizeof(inq));
    ds[s] = 0, rst[s] = inf;
    queue<int> q;
    q.push(s), inq[s] = 1;
    while (q.size())
    {
        int x = q.front();
        q.pop(), inq[x] = 0;
        for (int i = head[x]; i; i = nxt[i])
        {
            int y = to[i], c = cap[i], z = cost[i];
            if (!c)
                continue;
            if (ds[y] < ds[x] + z)
            {
                ds[y] = ds[x] + z, rst[y] = min(rst[x], c), pre[y] = i;
                if (!inq[y])
                    q.push(y), inq[y] = 1;
            }
        }
    }
    return ds[t] != (int)0xcfcfcfcf;
}

int max_cost_flow(int s, int t)
{
    int flow = 0, res = 0;
    while (spfa(s, t))
    {
        int x = t, f = rst[t], z = ds[t];
        while (x != s)
        {
            int i = pre[x];
            cap[i] -= f, cap[i ^ 1] += f, x = to[i ^ 1];
        }
        flow += f, res += f * z;
    }
    return res;
}

int main()
{
    scanf("%d%d", &N, &K);
    tot = 1;
    int bs = N * N;
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; ++j)
        {
            scanf("%d", &num[i][j]);
            id[i][j][0] = i * N + j, id[i][j][1] = bs + i * N + j;
        }
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < N; ++j)
        {
            add(id[i][j][0], id[i][j][1], 1, num[i][j]);
            add(id[i][j][0], id[i][j][1], K - 1, 0);
            if (i + 1 < N)
                add(id[i][j][1], id[i + 1][j][0], K, 0);
            if (j + 1 < N)
                add(id[i][j][1], id[i][j + 1][0], K, 0);
        }
    printf("%d\n", max_cost_flow(id[0][0][0], id[N - 1][N - 1][1]));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值