网络流24题(九)

网络流24题(九)

九、方格取数问题

题目描述

有一个 \(m\)\(n\) 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。

输入格式

第一行是两个用空格隔开的整数,分别代表方格图的行数 \(m\) 和列数 \(n\)

\(2\) 到第 \((m + 1)\) 行,每行 \(n\) 个整数,第 \((i + 1)\) 行的第 \(j\) 个整数代表方格图第 \(i\) 行第 \(j\) 列的的方格中的数字 \(a_{i, j}\)

输出格式

输出一行一个整数,代表和最大是多少。

PS:这个傻逼题目\(m\)\(n\)列,我的代码是\(n\)\(m\)列的

题解

模型:

二分图最大权独立集,这个问题等价于求二分图最小权覆盖集的补集,然后后者可以建图用最小割求。

下面解释一下,最小权覆盖集和最大权独立集。
覆盖集:在一张图上选若干个点,这些点满足他们的邻边覆盖整张图,那么这些点就是覆盖集,最小覆盖集就是点数最少的覆盖集合,最小权覆盖集就是如果我们给上点权,我们求出的权值和最小的覆盖集合。
独立集:在一张图上选若干个点,这些点满足两两之间没有边相连,相互独立,我们称这个点集合为独立集,当这个集合点数最多的时候就是最大独立集,如果给上点权,权值和最大的就是最大独立集。

覆盖集与独立集:如果\(V\)是一个覆盖集,那么满足所有边都有一个点至少在\(V\)集合中,那么显然,剩下的所有点都是不相连的,所以\(V\)的补集\(V'\)就是独立集,同样的最小权覆盖集的补集就是最大全独立集。

所以我们知道,如果我们要求最大权独立集可以考虑先求最小权覆盖集。

那么如何求最小权覆盖集呢?

\(Amber\)胡伯涛在论文《最小割模型在信息学竞赛中的应用》给了一个十分清晰的思考过程:

回顾与此模型相关的模型。简化权的条件后,可以借鉴的是二分图匹配的最大流解法。
它加入了额外的源 \(s\)和汇 \(t\) ,将匹配以一条条\(s-u-v-t\)形式的流路径“串联”起来。匹配的限制是在点上,恰当地利用了流的容量限制。而点覆盖集的限制在边上,最小割是最大流的对偶问题,对偶往往是将问题的性质从点转边,从边转点。可以尝试着转化到最小割模型。

在这个思路的启发下,我们开始考虑最小割覆盖集关系。
建图的时候参考二分图匹配时的建图,建立一个源点\(s\)和一个汇点\(t\),不难发现二分图中的每一条边\((u,v)\)都对应一条\(s-u-v-t\)的路径,在这样的一条路径当中势必会有一条边存在中。
根据割的定义,割会做到这样一件事情:取走割中的所有边,所有路径不连通。
所有路径?再联系之前说到一句每一条边\((u,v)\)对应一条\(s-u-v-t\)的路径,这样的话我们也找到了所有的边。这与覆盖集想要做的事情一模一样。
现在要求的是覆盖集,我们的注意力应该要放在点上,假设选择了\(u-v\)这样的边放在割中不能体现我们对点的选取,所以我们人为的让\(u-v\)这样的边容量限制为\(inf\),这样我们边的选取就是只会在\(s-u\)\(v-t\)之间进行。

这样我们就发现,覆盖集与割是一一对应的,所以最小覆盖集就是最小割。

最后就是给上权值,稍加思考就发现其实和之前并没有什么不同,只是建图时边的容量略加修改即可。

求最大独立集即是求最小覆盖集的补集。

建图与实现:

这张图是二分图:考虑黑白染色,黑色之外必定为白色,不难看出黑色与白色分别为二分图的两部分,如果在棋盘中相邻则连边,边得容量为\(inf\)

建立一个源点\(s\)一个汇点\(t\),源点\(s\)向黑色连边,边的容量为格子的得分,白色向汇点\(s\)连边,边的容量为格子的得分。

整理下思绪:

在二分图下:
最小覆盖集 = 最小割 = 最大流 = 最大匹配 = \(n\)-最大独立集

代码

#include <iostream>
#include <queue>
#include  <cstring>
#define ll long long

const ll N = 5e3+50,M = 5e4+50;
const ll inf = 0x3f3f3f3f;
using namespace std;
ll head[N] = {0}, cnt = 1;
struct Edge{
    ll to,w,nxt;
}edge[M*2];
void add(ll u,ll v,ll w){
    edge[++cnt] = {v,w,head[u]};
    head[u] = cnt;
}
void add2(ll u,ll v,ll w){
    //cout<<"u:"<<u<<"v:"<<v<<endl;
    add(u,v,w);
    add(v,u,0);
}
ll s,t,lv[N],cur[N];
bool bfs(){
    memset(lv,-1,sizeof lv);
    lv[s] = 0;
    memcpy(cur,head,sizeof head);
    queue<ll> q;q.push(s);
    while(!q.empty()){
        ll p = q.front();q.pop();
        for(ll eg = head[p];eg;eg = edge[eg].nxt){
            ll to = edge[eg].to,vol = edge[eg].w;
            //cout<<"from: "<<p<<" to:"<<to<<" vol: "<<vol<<endl;
            if(vol > 0 && lv[to] == -1){
                lv[to] = lv[p]+1;
                q.push(to);
            }
        }
    }
    return lv[t] != -1;
}

ll dfs(ll p = s,ll flow = inf){
    if(p == t)  return flow;
    ll rmn = flow;
    for(ll &eg = cur[p];eg;eg = edge[eg].nxt){
        if(!rmn) break;
        ll to = edge[eg].to,vol = edge[eg].w;
        if(vol > 0 && lv[to] == lv[p]+1){
            ll c = dfs(to,min(vol,rmn));
            rmn -= c;
            edge[eg].w -= c;
            edge[eg^1].w += c;
        }
    }
    return flow-rmn;
}

ll dinic(){
    ll ans = 0;
    while(bfs()) ans += dfs();
    return ans;
}
ll n,m,chess[N][N];

int main() {
    ll sum = 0;
    cin>>n>>m;
    s = 0,t = n*m+1;
    ll x = 0;
    for(ll i = 1;i <= n;i++) {
        for (ll j = 1; j <= m; j++) {
            x++;
            cin >> chess[i][j];
            sum += chess[i][j];
            if((i&1) == (j&1))add2(s, x, chess[i][j]);
            else add2(x,t,chess[i][j]);
        }
    }
    x = 0;
    for(ll i = 1;i <= n;i++){
        for(ll j = 1;j <= m;j++) {
            x++;
            if((i&1) != (j&1)) continue;
            if (i != 1)add2(x, x - m, inf);
            if (i != n)add2(x, x + m, inf);
            if (j != 1)add2(x, x - 1, inf);
            if (j != m)add2(x, x + 1, inf);
        }
    }
    cout<<sum-dinic()<<endl;
    return 0;
}

posted @ 2021-10-04 15:06  Paranoid5  阅读(29)  评论(0编辑  收藏  举报