【網絡流】解題報告: luogu P2045 方格取數加強版(k取方格數)(最大費用最大流)

【】
點邊轉化:把每個格子 (i,j) 拆成一個入點一個出點。
詳解鏈接:【圖論技巧】點邊轉化(拆點和拆邊)
從每個入點向對應的出點連兩條有向邊:一條容量爲 1 ,費用爲格子 (i,j) 中的數;
另一條容量爲 k−1 ,費用爲 0 。

從 (i,j) 的出點到 (i,j+1) 和 (i+1,j) 的入點連有向邊,容量爲 k ,費用爲 0 。
以 (1,1) 的入點爲源點, (n,n) 的出點爲匯點,求最大費用最大流。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e3+7;
const int M = 5e5+7;
int maxflow,s,t,k;
int n,m,ans,e;
int head[N],ver[M],nex[M],edge[M],cost[M],tot;
bool vis[N];
int dis[N],incf[N],pre[N];

void add(int x,int y,int z,int c){//正邊反邊
    ver[++tot] = y;edge[tot] = z;cost[tot] = c;
    nex[tot] = head[x];head[x] = tot;
    ver[++tot] = x;edge[tot] = 0;cost[tot] = -c;
    nex[tot] = head[y];head[y] = tot;
}

int num(int i,int j,int k){
    return (i - 1) * n + j + k * n * n;
}

bool spfa(){//spfa求最長路
    queue<int>q;
    memset(vis,0,sizeof vis);
    memset(dis,0xcf,sizeof dis);//-INF
    q.push(s);
    dis[s] = 0;vis[s] = 1;
    incf[s] = 1<<30;//增廣路各邊的最小剩餘容量
    while(q.size()){
        int x = q.front();q.pop();
        vis[x] = 0;//spfa的操作
        for(int i = head[x];i;i = nex[i]){
            if(edge[i]){//剩餘容量要>0,纔在殘餘網絡中
                int y = ver[i];
                if(dis[y] < dis[x] + cost[i]){
                    dis[y] = dis[x] + cost[i];
                    incf[y] = min(incf[x],edge[i]);//最小剩餘容量
                    pre[y] = i;//記錄前驅(前向星編號),方便找到最長路的實際方案
                    if(!vis[y])
                        vis[y] = 1,q.push(y);
                }
            }
        }
    }
    if(dis[t] == 0xcfcfcfcf)
        return false;//匯點不可達,已求出最大流
    return true;
}

//EK的老操作了,更新最長增廣路及其反向邊的剩餘容量
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];//成對變換,反邊加
        x = ver[i ^ 1];//反邊回去的地方就是上一個結點
    }
    maxflow += incf[t];//順便求最大流
    ans += dis[t] * incf[t];//題目要求
}

void EK(){
    while(spfa())//瘋狂找增廣路
        update();
}

int main(){
    cin>>n>>k;
    s = 1;t = 2 * n * n;
    tot = 1;
    over(i,1,n)
    over(j,1,n){
        int c;
        scanf("%d",&c);
        add(num(i,j,0),num(i,j,1),1,c);//自己(入點0)與自己(出點1)
        add(num(i,j,0),num(i,j,1),k-1,0);//兩條邊(取k次嘛,第一次有值,以後就沒值了,用作下次選取)
        if(i < n)add(num(i,j,1),num(i+1,j,0),k,0);//自己(出點1)與下一行(入點0)或者下一列(入點0)
        if(j < n)add(num(i,j,1),num(i,j+1,0),k,0);
    }
    EK();
    printf("%d\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章