天天看點

網絡流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\)之間進行。

這樣我們就發現,覆寫集與割是一一對應的,是以最小覆寫集就是最小割。

最後就是給上權值,稍加思考就發現其實和之前并沒有什麼不同,隻是建圖時邊的容量略加修改即可。

求最大獨立集即是求最小覆寫集的補集。

建圖與實作:

整理下思緒:

代碼

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