首页 > 技术文章 > 网络流板子

Vikyanite 2020-07-25 21:53 原文

Dinic模板 上界O(N*M2)    二分图用

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Edge{
    int to;
    int next;
    int w;
}edge[maxn*2];

int head[maxn];
int cur[maxn];//当前弧优化数组
int n, m, s, t, x;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
int node_num;

inline void init(){
    cnt = 0;
    memset(head, -1, sizeof head);
}

inline void Add_Edge(int u, int v, int w){
    edge[cnt].next = head[u];
    edge[cnt].to = v;
    edge[cnt].w = w;
    head[u] = cnt++;
}

inline bool Bfs(){
    for(int i = 1; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
    dis[s] = 0;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u = q.front(); q.pop();
        for(int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].to;
            if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
                dis[v] = dis[u] + 1;
                q.push(v);
            }
        }
    }
    return dis[t] != -1;
}

inline int dfs(int u, int flow){
    if(u == t) return flow;
    int del = flow;
    for(int i = cur[u]; i != -1; i = edge[i].next){
        cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
        int v = edge[i].to;
        if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
            int ans = dfs(v, min(del, edge[i].w));//木桶原理
            edge[i].w -= ans;//正向弧减增广流量
            edge[i ^ 1].w += ans;//反向弧加增广流量
            del -= ans;//总流量减增广流量
            if(del == 0) break;//总流量为0则不继续增广
        }
    }
    return flow - del;//返回本次增广的流量
}

inline int Dinic(){
    int ans = 0;
    while(Bfs()){
        for(int i = 1; i <= node_num; ++i) cur[i] = head[i];
        ans += dfs(s, inf);
    }
    return ans;
}

int main(){
    init();
    scanf("%d", &n);
    s = 1, t = 26;
    node_num = 100;
    for (int i = 1; i <= n; i++) {
        char tu, tv;
        int u, v, w;
        scanf("\n%c %c %d", &tu, &tv, &w);
        u = tu-64, v = tv-64;
        Add_Edge(u, v, w);
        Add_Edge(v, u, 0);//正反向建图
    }
    printf("%d\n", Dinic());
    return 0;
}
View Code

ISAP模板 上界O(N2*M)   非二分图用

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
//const int maxm = 1e3 * 2 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int m,n,s,t, x, head[maxn], num_edge;
int cur[maxn],deep[maxn],last[maxn],num[maxn];
int node_num;
int cap[40];
int wanted[maxn][40];
//cur当前弧优化; last该点的上一条边; num桶 用来GAP优化
struct Edge{
    int next,to,dis;
}edge[maxn*2];

void add_edge(int from,int to,int dis)
{
    edge[num_edge].to=to;
    edge[num_edge].dis=dis;
    edge[num_edge].next=head[from];
    head[from]=num_edge++;
}
//bfs仅用于更新deep
void bfs(int t)
{
    queue<int>q;
    for (int i=0; i<=node_num; i++) cur[i]=head[i];
    for (int i=1; i<=node_num; i++) deep[i]=node_num;
    deep[t]=0;
    q.push(t);
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        for (int i=head[now]; i != -1; i=edge[i].next)
        {
            if (deep[edge[i].to]==node_num && edge[i^1].dis)//i^1是为了找反边
            {
                deep[edge[i].to] = deep[now]+1;
                q.push(edge[i].to);
            }
        }
    }
}

int add_flow(int s,int t)
{
    int ans=inf,now=t;
    while (now!=s)
    {
        ans=min(ans,edge[last[now]].dis);
        now=edge[last[now]^1].to;
    }
    now=t;
    while (now!=s)
    {
        edge[last[now]].dis-=ans;
        edge[last[now]^1].dis+=ans;
        now=edge[last[now]^1].to;
    }
    return ans;
}
int isap(int s,int t)
{
    int now=s;
    int maxflow = 0;
    bfs(t);//搜出一条增广路
    for (int i=1; i<=node_num; i++) num[deep[i]]++;
    while (deep[s]<node_num)
    {
        if (now==t)
        {//如果到达汇点就直接增广,重新回到源点进行下一轮增广
            maxflow+=add_flow(s,t);
            now=s;//回到源点
        }
        bool has_find=0;
        for (int i=cur[now]; i!=-1; i=edge[i].next)
        {
            if (deep[now]==deep[edge[i].to]+1 && edge[i].dis)//找到一条增广路
            {
                has_find=true;
                cur[now]=i;//当前弧优化
                now=edge[i].to;
                last[edge[i].to]=i;
                break;
            }
        }
        if (!has_find)//没有找到出边,重新编号
        {
            int minn=node_num-1;
            for (int i=head[now]; i!=-1; i=edge[i].next)//回头找路径
                if (edge[i].dis)
                    minn=min(minn,deep[edge[i].to]);
            if ((--num[deep[now]])==0) break;//GAP优化 出现了断层
            num[deep[now]=minn+1]++;
            cur[now]=head[now];
            if (now!=s)
                now=edge[last[now]^1].to;
        }
    }
    return maxflow;
}

void init()
{
    num_edge = 0;
    memset(head,-1,sizeof(head));
}

int main()
{
    init();
    scanf("%d", &n);
    s = 1, t = 26;
    node_num = 100;
    for (int i = 1; i <= n; i++) {
        char tu, tv;
        int u, v, w;
        scanf("\n%c %c %d", &tu, &tv, &w);
        u = tu-64, v = tv-64;
        add_edge(u, v, w);
        add_edge(v, u, 0);//正反向建图
    }
    printf("%d\n", isap(s, t));
    return 0;
}
View Code

 

原始对偶(Primal-Dual)费用流算法  可处理有负边无负环

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 5005, M = 50005;
const int INF = 0x3f3f3f3f;

int n, m, S, T;
int tot = -1, head[N], cur[N];
int h[N], dis[N], vis[N];

struct Edge {
    int p, nxt, c, w;
    Edge(int p = 0, int nxt = 0, int c = 0, int w = 0)
        : p(p), nxt(nxt), c(c), w(w) {}
} edge[M * 2];

inline void Add_Edge(int u, int v, int c, int w) {
    edge[++tot] = Edge(v, head[u], c, w);
    head[u] = tot;

    edge[++tot] = Edge(u, head[v], 0, -w);
    head[v] = tot;
}



bool Dijkstra() {
    for (int i = 1; i <= T; ++ i) dis[i] = INF;
    priority_queue<PII> pq;
    pq.push({dis[S] = 0, S});
    while (!pq.empty()) {
        PII cur = pq.top();
        pq.pop();
        int u = cur.second;
        if (-cur.first > dis[u]) continue;
        for (int i = head[u]; ~i; i = edge[i].nxt) {
            int v = edge[i].p, c = edge[i].c, w = edge[i].w + h[u] - h[v];
            if (c && dis[v] > dis[u] + w) pq.push({-(dis[v] = dis[u] + w), v});
        }
    }
    return dis[T] < INF;
}



int DFS(int u, int c) {
    if (u == T) return c;
    int r = c;
    vis[u] = 1;
    for (int &i = cur[u]; ~i && r; i = edge[i].nxt) {
        int v = edge[i].p, c = edge[i].c, w = edge[i].w + h[u] - h[v];
        if (!vis[v] && c && dis[u] + w == dis[v]) {
            int x = DFS(v, min(r, c));
            r -= x;
            edge[i].c -= x;
            edge[i ^ 1].c += x;
        }
    }
    vis[u] = 0;
    return c - r;
}
/*
6 11
1 2 23
1 3 12
1 4 99
2 5 17
2 6 73
3 5 3
3 6 21
4 6 8
5 2 33
5 4 5
6 5 20
*/

int main() {
    scanf("%d%d", &n, &m);
    S = 1, T = n*2;
    memset(head, -1, sizeof(head));

    Add_Edge(n, T, 2, 0);
    Add_Edge(S, n+1, 2, 0);
    for (int i = 2; i <= n-1; ++ i)
        Add_Edge(i, i+n, 1, 0);
    for (int i = 1; i <= m; ++i) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        Add_Edge(u+n, v, 1, w);
    }
    int mf = 0, mc = 0;
    while (Dijkstra()) {
        memcpy(cur, head, sizeof(cur));
        int c = DFS(S, INF);
        for (int i = 1; i <= T; ++i)
            if (dis[i] < INF) h[i] += dis[i];
        mf += c;
        mc += c * h[T];
    }
    printf("%d %d\n", mf, mc);
    return 0;
}
View Code

 上面那个是直接用dij跑的

正规的应该是用Bellman-ford先预处理之后再dij,附上代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll, int> pii;

struct Edge {
    int u, v;
    ll flow, cap, cost;
    int next;
};

const int MAXN = 5000, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;

int e_ptr = 1, S, T, n, m, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];

void AddEdge(int u, int v, ll cap, ll cost) {
    E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
    E[++e_ptr] = (Edge) { v, u, 0,  0, -cost, head[v] }; head[v] = e_ptr;
}

void Reduce() {
    for(int i = 2; i <= e_ptr; i++)
        E[i].cost += dist[E[i].v] - dist[E[i].u];
    delta += dist[S];
}

bool BellmanFord() {
    queue<int> Q;
    memset(dist, 0x3f, sizeof(dist));
    dist[T] = 0; Q.push(T); inq[T] = true;
    while(!Q.empty()) {
        int u = Q.front(); Q.pop(); inq[u] = false;
        for(int j=head[u]; j; j=E[j].next) {
            int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
            if(f < c && dist[v] > dist[u] + len) {
                dist[v] = dist[u] + len;
                if(!inq[v]) {
                    inq[v] = true;
                    Q.push(v);
                }
            }
        }
    }
    return dist[S] != LLINF;
}

bool Dijkstra() {
    memset(dist, 0x3f, sizeof(dist));
    memset(vis, 0, sizeof(vis));
    priority_queue<pii,vector<pii>,greater<pii> > pq;
    dist[T] = 0; pq.push({dist[T], T});
    while(!pq.empty()) {
        int u = pq.top().second; pq.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int j=head[u]; j; j=E[j].next) {
            int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
            if(f < c && dist[v] > dist[u] + len) {
                dist[v] = dist[u] + len;
                pq.push({dist[v],v});
            }
        }
    }
    return dist[S] != LLINF;
}

ll DFS(int u, ll flow) {
    if(u == T || flow == 0) return flow;
    vis[u] = true; // differ from dinic
    ll res = flow;
    for(int j=head[u]; j; j=E[j].next) {
        int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
        if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
            ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
            E[j].flow += tmp;
            E[j^1].flow -= tmp;
            res -= tmp;
        }
    }
    return flow - res;
}

void Augment() {
    ll CurFlow = 0;
    while(memset(vis, 0, sizeof(vis)),
        (CurFlow = DFS(S, LLINF))) {
        MaxFlow += CurFlow;
        MinCost += CurFlow * delta;
    }
}

void PrimalDual() {
    if(!BellmanFord()) return;
    Reduce(); Augment();
    while(Dijkstra()) {
        Reduce(); Augment();
    }
}

int main() {
    int u, v, cap, cost;
    scanf("%d%d%d%d", &n, &m, &S, &T);
    for(int i=1; i<=m; i++) {
        scanf("%d%d%d%d", &u, &v, &cap, &cost);
        AddEdge(u, v, cap, cost);
    }
    PrimalDual();
    printf("%lld %lld", MaxFlow, MinCost);
    return 0;
}
View Code

 

但是对于动态开点的题目貌似上面的代码不是很好用。所以存一个动态开点费用流

#include <iostream>
#include <iomanip>
#include <cstring>
#include <map>
#include <queue>
#define inf 2147483646
#define N 10000
using namespace std;

struct ed{
    int u,w,next,f;
}e[1000000];
int g[1000][2000],a[2000];
int n,m,st=1,ans,cost,sm,fir[30000],c[30000],d[30000];
int vis[30000],sp[30000];
queue<int> q; bool v[30000]; 
map<int,int> ha;

void add(int x,int y,int w,int f)
{
    e[++st].u=y; e[st].next=fir[x]; e[fir[x]=st].w=w; e[st].f=f;
    e[++st].u=x; e[st].next=fir[y]; e[fir[y]=st].w=0; e[st].f=-f;
}

bool spfa()
{
    for (int i=0;i<=N;i++) d[i]=inf/2,c[i]=fir[i],v[i]=0;
    q.push(0); v[0]=1; d[0]=0;
    while (!q.empty())
    {
        int k=q.front(); q.pop();  v[k]=0;
        for (int i=fir[k];i;i=e[i].next){
            int u=e[i].u,w=e[i].f;
            if (d[u]>d[k]+w&&e[i].w){
                d[u]=d[k]+w; if (!v[u]) v[u]=1,q.push(u);
            }
        }
    } 
    return (d[N]<inf/2);
}

int dfs(int p,int now)
{
    if (p==N){v[N]=1; return now;}
    int mw=0;  v[p]=1;
    for (int i=fir[p];i;i=e[i].next)
    {
        int u=e[i].u,w=e[i].f; 
        if (d[u]==d[p]+w&&e[i].w&&(!v[u]||u==N))
        if (mw=dfs(u,min(e[i].w,now)))
        {
            e[i].w-=mw; e[i^1].w+=mw; 
            cost+=mw*w; return mw;
        }
    }
}

void dinic()
{
    while (spfa()) {
         ans+=dfs(0,inf);
         for (int i=fir[N];i;i=e[i].next){
             int u=e[i].u,w=e[i].w;
             if (w&&!vis[u]) {
                 vis[u]=1; int co=ha[u]; sp[co]++;
                 add(++sm,N,1,0); ha[sm]=co;
                 for (int i=1;i<=n;i++) add(i,sm,1,sp[co]*g[i][co]);
             }
         }
    }
}

int main()
{
    cin>>n>>m; int sum=0;
    for (int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
    for (int i=1;i<=n;i++) 
    for (int j=1;j<=m;j++) cin>>g[i][j];
    
    for (int i=1;i<=n;i++) add(0,i,a[i],0);
    sm=n;
    //for (int k=1;k<=n;k++) 时间K(总数不为n了) 
    for (int j=1;j<=m;j++) {//厨师j 
        add(++sm,N,1,0); ha[sm]=j; sp[j]=1;
        for (int i=1;i<=n;i++) add(i,sm,1,g[i][j]); //菜i 
    }
    dinic();
    cout<<cost<<endl; 
}
View Code

 

 

存一个最小费用循环流的代码:

看了紫书看半天都没搞懂这个东西要求什么。

那就只能总结一下这个题目的特点,看看这个算法到底什么时候能套吧。

给出一张有向图,从中选出权和最大的边集,组成若干个有向环。

方法是根据环来增广(我也不懂我在讲什么

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int N=110;
const int M=20010;
const int INF=1061109567;
const double dINF=1e9;
const double eps=1e-6;
int w[N],cnt,fir[N],to[M],nxt[M];
int cap[M],path[N],vis[N];
double val[M],dis[N];
queue<int>q;
struct Net_Flow{
    void Init(){memset(fir,0,sizeof(fir));cnt=1;}

    void add(int a,int b,int c,double v){
        nxt[++cnt]=fir[a];cap[cnt]=c;
        to[cnt]=b;val[cnt]=v;fir[a]=cnt;
    }

    void addedge(int a,int b,int c,double v){
        add(a,b,c,v);add(b,a,0,-v);
    }

    double Spfa(int S,int T){
        for (int i = 0; i <= T+1; ++ i) dis[i] = dINF;
        q.push(S);vis[S]=1;dis[S]=0;
        while(!q.empty()){
            int x=q.front();q.pop();vis[x]=0;
            for(int i=fir[x];i;i=nxt[i])
                if(cap[i]&&dis[to[i]]-dis[x]-val[i]>eps){
                    dis[to[i]]=dis[x]+val[i];
                    if(!vis[to[i]])q.push(to[i]);
                    vis[to[i]]=1;path[to[i]]=i;
                }
        }
        return dis[T];
    }

    int Aug(int S,int T){
        int p=T,f=INF;
        while(p!=S){
            f=min(f,cap[path[p]]);
            p=to[path[p]^1];
        }p=T;
        while(p!=S){
            cap[path[p]]-=f;
            cap[path[p]^1]+=f;
            p=to[path[p]^1];
        }
        return f;
    }

    double MCMF(int S,int T){
        double v=0,d;
        while((d=Spfa(S,T))!=dINF)
            v+=d*Aug(S,T);
        return v;
    }
}mcmf;
int deg[N];
int a[N],b[N],G[N][N];
int sqr(int x){
    return x*x;
}
int main(){
    int n,x,y,cas=0;double ans;
    while(scanf("%d%d%d",&n,&x,&y)!=EOF){
        if(!n)break;
        mcmf.Init();ans=0.0;
        memset(G,0,sizeof(G));
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=n;i++){
            int t;
            scanf("%d%d%d",&a[i],&b[i],&t);
            while(t){G[i][t]=1;scanf("%d",&t);}
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)if(G[i][j]){
                double v=y-x*sqrt(sqr(a[i]-a[j])+sqr(b[i]-b[j]));
                if(v<0){
                    mcmf.addedge(j,i,1,-v);
                    ans+=v;deg[j]+=1;deg[i]-=1;
                }
                if(v>0)mcmf.addedge(i,j,1,v);
            }

        for(int i=1;i<=n;i++){
            if(deg[i]>0)mcmf.addedge(0,i,deg[i],0);
            if(deg[i]<0)mcmf.addedge(i,n+1,-deg[i],0);
        }
        printf("Case %d: %.2f\n",++cas,eps-ans-mcmf.MCMF(0,n+1));
    }
    return 0;
}
View Code

 

推荐阅读