洛谷 P3376 【模板】网络最大流 (EK/Dinic)

网络流的模板题。

// EK algorithm 复杂度 O(NM^2)
// 整体思路是通过不断地bfs找增广路径,然后通过增广路径扩充最大流
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define MAXN 250
#define MAXM 10010
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,m,s,t,u,v,cnt,head[MAXN],p[MAXN],a[MAXN][MAXN];
ll w,d[MAXN],res;bool vis[MAXN];
struct Edge{
    int to,nxt;ll w;
}edge[MAXM];
void addedge(int u,int v,ll w){
    edge[cnt].to=v;edge[cnt].w=w;edge[cnt].nxt=head[u];head[u]=cnt++; // 正向边
    edge[cnt].to=u;edge[cnt].w=0;edge[cnt].nxt=head[v];head[v]=cnt++; // 反向边,初始容量为0
}
int bfs(){
    memset(vis,false,sizeof(vis));
    queue<int> q;q.push(s);vis[s]=true;d[s]=INF; // 源点入队
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;ll w=edge[i].w;
            if(!w||vis[v])continue; // 保证还有容量并且未访问过
            d[v]=min(d[u],w);p[v]=i;q.push(v);vis[v]=true; // 更新最大流
            if(v==t)return 1; // 找到了一条增广路
        }
    }
    return 0;
}
void update(){
    int u=t; // 从汇点出发
    while(u!=s){
        int i=p[u]; // 存下来的路径对应的边
        edge[i].w-=d[t]; // 更新边的容量
        edge[i^1].w+=d[t]; // 更新反边的容量
        u=edge[i^1].to; // 通过反边找到路径上的下一个结点
    }
    res+=d[t]; // 更新最大流
}
int main(){
#ifdef WINE
    freopen("data.in","r",stdin);
#endif
    memset(head,-1,sizeof(head));
    memset(a,-1,sizeof(a));
    scanf("%d%d%d%d",&n,&m,&s,&t); // 点数,边数,源点,终点
    for(int i=0;i<m;i++){
        scanf("%d%d%lld",&u,&v,&w); // 有向边及其容量
        if(a[u][v]==-1){ // 处理重边
            addedge(u,v,w);a[u][v]=cnt-2; // -2 跟链式前向星的写法有关
        }else edge[a[u][v]].w+=w; // 重边,累计容量
    }
    while(bfs())update(); // 不断寻找增广路,然后更新最大流
    printf("%lld",res);
    return 0;
}

// Dinic+当前弧优化+剪枝 O(N^2M)
// 是 EK (O(NM^2))的改进
// 每次 bfs 建立一个分层图
// 然后用 dfs 通过扩充多条增广路径
// 稀疏图中效率和 EK 差不多,但是在稠密图中有优势,是网络流最常用的算法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define MAXN 300
#define MAXM 10010
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
int n,m,s,t,u,v,cnt,head[MAXN],cur[MAXN];
ll w,d[MAXN],res;
struct Edge{
    int to,nxt;ll w;
}edge[MAXM];
void addedge(int u,int v,ll w){
    edge[cnt].to=v;edge[cnt].w=w;edge[cnt].nxt=head[u];head[u]=cnt++;
    edge[cnt].to=u;edge[cnt].w=0;edge[cnt].nxt=head[v];head[v]=cnt++;
}
int bfs(){ // 在 residual graph 中构造分层图
    for(int i=1;i<=n;i++)d[i]=INF;
    queue<int> q;q.push(s);d[s]=0;cur[s]=head[s];
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;ll w=edge[i].w;
            if(!w)continue;
            if(d[v]==INF){ // cur 存的是当前弧优化,可见,每次bfs都会将cur更新成初始状态
                q.push(v);cur[v]=head[v];d[v]=d[u]+1;
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
ll dfs(int u,ll sum){
    if(u==t)return sum;
    ll k,res=0;
    for(int i=cur[u];i!=-1&&sum;i=edge[i].nxt){
        cur[u]=i; // 当前弧优化,这边可以这样理解:有两条路径可以到达这一节点
        // 其中第一条路径上的流量不断地找当前结点的流出路径来进行流动,随着分配流出路径的进行
        // sum (残余流量)在不断地减小,
        // 结果 sum 在标号为 i 的流出路径中用完了(但是该流出路径仍然有可能含有剩余容量)
        // 之后通过第二条路径再到达当前结点时,标号为 i 的流出路径之前的那些流出路径不需要再进行考虑
        // (因为已经被第一条路径喂饱了),所以当前弧优化就是为了保证下次再经过当前结点时,
        // 可以直接从上一次最后分配到的流出路径开始喂。
        // 可以想到,假如某个结点的流出路径非常多,并且每条路径容量都比较小,
        // 那么当前弧优化将减少大量不必要的判断
        int v=edge[i].to;ll w=edge[i].w;
        if(!w)continue;
        if(d[v]==d[u]+1){
            k=dfs(v,min(sum,w));
            if(!k)d[v]=INF; // 剪枝,从 v 往下走的路已经不通,那就不要再让后面的兄弟来再探一次了
            edge[i].w-=k;edge[i^1].w+=k;
            res+=k;sum-=k;
        }
    }
    return res;
}
int main(){
#ifdef WINE
    freopen("data.in","r",stdin);
#endif
    memset(head,-1,sizeof(head));
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<m;i++){
        scanf("%d%d%lld",&u,&v,&w);
        addedge(u,v,w);
    }
    while(bfs())res+=dfs(s,INF);
    printf("%lld",res);
    return 0;
}

posted @ 2020-07-15 10:22  winechord  阅读(80)  评论(0编辑  收藏  举报