模板 - 费用流

整理的算法模板合集: ACM模板


文章目录

  • 一、最小费用最大流
    • 类dinic模板
  • 二、最大费用最大流
    • 解决二分图带权最大匹配
  • 三、费用提前计算+动态开点

一、最小费用最大流

类dinic模板

时间复杂度为 O ( n m f ) O(nmf) O(nmf) f f f 为最大流量, 效率较高,一般不会被卡

#include
#include
#include
#include
#include

using namespace std;
typedef long long ll;

namespace dinic{//MCMF

    const int N = 1e5 + 7, M = 2e6 + 7;
    const ll INF = 0x3f3f3f3f3f;//!因为是long long 所以是五个3f
    int n, S, T;
    int head[N], ver[M], nex[M], tot, cur[N];
    ll dist[N], edge[M], cost[M], maxflow, mincost;
    bool vis[N];

    inline void add(int x, int y, ll z, ll c, bool o = 1){
        ver[tot] = y;
        edge[tot] = z;
        cost[tot] = c;
        nex[tot] = head[x];
        head[x] = tot ++ ;
        if(o)add(y, x, 0, -c, 0);
    }

    inline bool spfa(){//对费用 cost 求最短路
        //memset(dist, 0x3f, sizeof dist);//!0x3f3f3f3f3f -> 3f x 5
        for(int i = 1;i <= n; ++ i)dist[i] = INF;
        memset(vis, 0, sizeof vis);
        queue<int>q;
        q.push(S);
        dist[S] = 0;
        vis[S] = 1;
        while(q.size()){
            int x = q.front();
            q.pop();
            vis[x] = 0;
            for(int i = head[x]; ~i; i = nex[i]){
                int y = ver[i];
                ll z = edge[i], c = cost[i];
                if(dist[y] <= dist[x] + c || !z)continue;
                dist[y] = dist[x] + c;
                if(!vis[y])
                    q.push(y), vis[y] = 1;
            }
        }
        return dist[T] != INF;
    }


    ll dfs(int x, ll flow = INF){
        if(x == T)return flow;
        ll ans = 0, k, i;
        vis[x] = 1;
        for(i = cur[x]; ~i && flow; i = nex[i]){
            cur[x] = i;
            int y = ver[i];
            ll z = edge[i], c = cost[i];
            if(!z || (dist[y] != dist[x] + c) || vis[y])continue;
            k = dfs(y, min(flow, z));
            if(!k)dist[y] = INF;
            edge[i] -= k;
            edge[i ^ 1] += k;
            ans += k, mincost += k * c, flow -= k;
        }
        vis[x] = 0;
        return ans;
    }

    inline void main(){
        while(spfa()){
            for(int i = 1; i <= n; ++ i)
                cur[i] = head[i];
            //memcpy(cur, head, sizeof head);
            ll now;
            while((now = dfs(S)))maxflow += now;//!
        }
    }

    inline void init(int _n, int _S, int _T){
        n = _n, S = _S, T = _T, tot = 0, maxflow = 0, mincost = 0;
        memset(head, -1, sizeof head);
    }
}


int n, m, S, T;
int main(){
    scanf("%d%d%d%d", &n, &m, &S, &T);
    dinic::init(n, S, T);

    for(int i = 1; i <= m; ++ i){
        int x, y;
        ll z, c;
        scanf("%d%d%lld%lld", &x, &y, &z, &c);
        dinic::add(x, y, z, c, 1);
    }
    dinic::main();
    printf("%lld %lld\n", dinic::maxflow, dinic::mincost);
    return 0;
}

二、最大费用最大流

解决二分图带权最大匹配

类似最大流解决二分图多重匹配,每条边的权值就是他的单位费用。

给定一个 n×n 的矩阵,每一格有一个非负整数 ai,j。现在从 (1,1) 出发,可以往右或者往下走,最后到达
(n,n)。每到达一格,把该格子的数取出来,该格子的数就变成 0。这样一共走 k 次,现在要求 k 次所达到的方格的数的和最大。

  • 点边转化:把每个格子 (i,j) 拆成一个入点一个出点。
  • 从每个入点向对应的出点连两条有向边:一条容量为 1,费用为格子 ai,j; 另一条容量为 k−1,费用为 0。
  • 从 (i,j) 的出点到 (i,j+1) 和 (i+1,j) 的入点连有向边,容量为 k,费用为 0。
  • 以 (1,1) 的入点为源点,(n,n) 的出点为汇点,求最大费用最大流。
const ll INF = 1e18;
const int N = 5e3+7, 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;
}

三、费用提前计算+动态开点

你可能感兴趣的:(【ACM模板】,#,费用流)