jzoj5823 marshland (最大费用可行流)

86 篇文章 0 订阅
11 篇文章 0 订阅

题面

这里写图片描述
n<=50

巧妙构图

想了好久流还是不会
注意题目给的奇偶性质 : 一个L中间必定X+Y=奇数。
显然L除了中间之外的旁边的两个点一定是X+Y=偶数的。
仔细观察bing就bu能发现,这两个点一个是偶+偶,一个是奇+奇。
根据这个构图,就变成了简单的最大费用可行流。
别忘了最大流量限制为m.
关于可行流,有一个结论是,当最长路小于0时,不再进行增广。
感性理解一下,不会由于当前的负增广而出现更大的正增广。
费用流采用的是多路增广,避免重点的方法。

实现

#include <cstdio>
#include <iostream>
#include <cstring>
#define min(a,b) ((a)>(b)?(b):(a))
using namespace std;
typedef long long ll;
const ll N = 55, E = N * N * 10;
ll n,m,k,dan[N][N],id[N][N],vt,wid[N][N];
ll final[N * N * 2], S, T, to[E], nex[E], cost[E], f[E], tot, ban[N * N * 2];
ll s,SS;

void link0(ll x,ll y,ll fl,ll w) {
    to[++tot] = y,nex[tot] = final[x],final[x]=tot;
    cost[tot] = w,f[tot] = fl;
}

void link(ll x,ll y,ll fl,ll w) {
    if (!x||!y||ban[x]||ban[y]) return;
    link0(x,y,fl,w);
    link0(y,x,0,-w);
}

ll Q[N * N * 20], dis[N * N * 2], vis[N * N * 2],step[N * N * 2];

void spfa() {
    ll L = 0, R = 0; Q[R = 1] = SS;
    memset(dis,128,sizeof dis); dis[SS] = 0;
    while (L < R) {
        ll x = Q[++L]; vis[x] = 0;
        for (ll i = final[x]; i; i=nex[i]) if (f[i]) {
            ll y = to[i];
            if (cost[i] + dis[x] > dis[y]) {
                dis[y] = cost[i] + dis[x];
                if (!vis[y]) 
                    Q[++R] = y, vis[y] = 1;
            }
        }
    }
}

ll go(ll x,ll wait) {
    if (x == T) 
        return wait;
    ll used = 0; vis[x] = 1;
    for (ll i = final[x]; i; i=nex[i]) 
        if (f[i] && dis[to[i]] == dis[x] + cost[i]) {
            ll y = to[i];
            if (!vis[y]) {
                ll take = go(y, min(wait - used, f[i]));
                s -= cost[i] * take;
                f[i] -= take, f[i^1] += take;
                used += take;
                if (used == wait) break;
            }
        }
    vis[x] = 0;
    return used;
}

int main() {
    freopen("marshland.in","r",stdin);
    // freopen("marshland.out","w",stdout);
    tot = 1;
    cin>>n>>m>>k;
    for (ll i = 1; i <= n; i++) for (ll j = 1; j <= n; j++) {
        scanf("%lld",&dan[i][j]), id[i][j] = ++vt;
        s+=dan[i][j];
        if ((i+j)&1) wid[i][j] = ++vt;
    }

    for (ll i = 1; i <= k; i++) {
        ll x,y; scanf("%lld %lld",&x,&y);
        ban[id[x][y]]=1;
    }

    S=vt+1,T=vt+2,SS=vt+3;
    link(SS,S,1e9,0);

    for (ll i = 1; i <= n; i+=2) for (ll j = 1; j <= n; j+=2) 
        link(S, id[i][j], 1, 0);

    for (ll i = 2; i <= n; i+=2) for (ll j = 2; j <= n; j+=2) 
        link(id[i][j], T, 1, 0);

    for (ll i = 1; i <= n; i+=2) for (ll j = 2; j <= n; j+=2) {
        link(id[i][j], wid[i][j], 1, dan[i][j]);

        link(id[i][j-1], id[i][j], 1, 0);
        link(id[i][j+1], id[i][j], 1, 0);

        link(wid[i][j], id[i-1][j], 1, 0);
        link(wid[i][j], id[i+1][j], 1, 0);
    }

    for (ll i = 2; i <= n; i+=2) for (ll j = 1; j <= n; j+=2) {
        link(id[i][j], wid[i][j], 1, dan[i][j]);

        link(wid[i][j], id[i][j-1], 1, 0);
        link(wid[i][j], id[i][j+1], 1, 0);

        link(id[i-1][j], id[i][j], 1, 0);
        link(id[i+1][j], id[i][j], 1, 0);
    }

    int cnt = 0;
    while (spfa(), 1) {
        cnt++;
        go(SS, 1e9);
        if (cnt == 517) {
            int kkk = cnt;
        }
    }
    cout<<s<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值