【暖*墟】#洛谷网课1.29# 图与网络流

 二分图匹配

 二分图相关结论

 

 匈牙利算法

int linker[MAXN * 2]; //右侧点的左侧匹配点

bool used[MAXN * 2]; //用于dfs标记访问

bool dfs(int u) {
    for (int i = head[u]; i; i = e[i].nextt) {
        int v = e[i].ver;
        if (!used[v]) { used[v] = true;
            if (linker[v] == -1 || dfs(linker[v])) 
             {  linker[v] = u; return true; } }
    } return false;
}

int hungarian(int n) {
    int res = 0; for (int i = 0; i <= n * 2; i++) linker[i] = -1;
    for (int u = 1; u <= n; u++) { //每次新加一点、判断是否有增广路
        for (int i = 1; i <= n * 2; i++) used[i] = 0;
        if (dfs(u)) res++; //↑↑需要多次清空used数组
    }  return res; //二分图最大匹配
}         

 KM算法的扩展

 网络流模型

有向图;源点S,汇点T;边流量<=容量;反对称性;流守恒 --> 流量最大的可行流

 网络流建图

【反向边】此边上已经走过的流量;【正向边】不断减小,表示还能走过的流量 -> 构成残量网络。

【增广路】残量网络中,若存在一条s->t的路径,且每条边权值>0,

                说明可以通过这条路径增加原网络的流量。

  • 经过反向边的增广路相当于减小原来那条边的容量,便于增广路的判断。

把边权(容量)为0的边隐藏起来(可以不用考虑),得到:

 Edmonds-Karp算法

 缺点:时间慢;优点:可以处理带权流的问题。

 Dinic算法

bfs将图分层,dfs寻找“阻塞”(增广路)。

struct Edge {
    int from, to,rev;
    int cap, flow; //容量,流量
    Edge(){}
    Edge(int _from, int _to, int _cap, int _flow, int _rev):
       from(_from), to(_to), cap(_cap), flow(_flow), rev(_rev) {};
}; vector<Edge> g[MAXN];

int cur[MAXN]; //当前点已经处理完了一部分下层点,从后方继续

inline void insert(int u, int v, int c) {
    g[u].push_back(Edge(u, v, c, 0, g[v].size()));
    g[v].push_back(Edge(v, u, 0, 0, g[u].size()-1));
}

int d[MAXN],q[MAXN], qhead, qtail;

int bfs() { //用bfs实现“分层”
    memset(d, 0, sizeof(d)); //dep
    qhead = qtail = 0, q[qtail++] = s, d[s] = 1;
    while (qhead != qtail) {
        int now = q[qhead++];
        for (auto e : g[now]) 
            if (!d[e.to] && e.cap > e.flow) 
               d[e.to] = d[now] + 1, q[qtail++] = e.to;
    } return d[t];
}

int dfs(int now, int a) { //a:此点剩余流量
    if (now == t || !a) return a; int flow = 0;
    for (int &i = cur[now]; i < g[now].size(); i++) {
        Edge &e = g[now][i]; //↑↑ &i=cur[now] 即:从上次停下来的地方继续
        if (d[e.to] == d[now] + 1 && e.cap > e.flow) { //有增广路到达下层
            int f = dfs(e.to, min(a, e.cap - e.flow)); 
           //↑↑ dfs(下个点流量,min(此点剩余流量,此边剩余流量));
            a -= f, flow += f, e.flow += f, g[e.to][e.rev].flow -= f;
        } if (!a) break;
    } if (a) d[now] = -1; return flow; //把多余流量放到全部的最前面,防止出现干扰
}

int dinic() {
    int flow = 0;
    while (bfs()) {
        memset(cur, 0, sizeof(cur));
        flow += dfs(s, INF);
    } return flow;
}

 最大流模型

相关练习题

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

//【最大流】dinic:bfs求分层图 + dfs求增广路

//1.根据从源点开始的bfs序列,为每一个点分配一个深度;(不会向回流)
//2.进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=1000019,INF=99999999;

int s,t,tot=-1,n,m,head[N],dep[N]; //s为源点,t为汇点 

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y,int z)
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

int bfs(){
    memset(dep,0,sizeof(dep)); //dep记录深度 
    queue<int> q; while(!q.empty()) q.pop();
    dep[s]=1; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=e[i].nextt)
            if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
    } if(dep[t]!=0) return 1;
      else return 0; //此时不存在分层图也不存在增广路
}

int dfs(int u,int lastt){
    if(u==t) return lastt; //lastt:此点还剩余的流量
    for(int i=head[u];i!=-1;i=e[i].nextt) 
        if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
            int f=dfs(e[i].ver,min(lastt,e[i].w)); 
            if(f>0){ e[i].w-=f,e[i^1].w+=f; return f; }
    } return 0; //没有dfs>0即说明没有增广路,返回0
}

int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,INF); return ans; }

int main(){
    reads(n),reads(m),reads(s),reads(t);
    int x,y,z; memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        reads(x),reads(y),reads(z),
        add(x,y,z),add(y,x,0); //反边初始流量为0
    } cout<<dinic()<<endl; return 0;
}
P3376 【模板】网络最大流 //dinic算法
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

/*【p2740】草地排水 */

//【标签】网络流、最大流Dinic算法

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=1000019,INF=99999999;

int s,t,tot=-1,n,m,head[N],dep[N]; //s为源点,t为汇点 

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y,int z)
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

int bfs(){
    memset(dep,0,sizeof(dep)); //dep记录深度 
    queue<int> q; while(!q.empty()) q.pop();
    dep[s]=1; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=e[i].nextt)
            if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
    } if(dep[t]!=0) return 1;
      else return 0; //此时不存在分层图也不存在增广路
}

int dfs(int u,int lastt){
    if(u==t) return lastt; //lastt:此点还剩余的流量
    for(int i=head[u];i!=-1;i=e[i].nextt) 
        if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
            int f=dfs(e[i].ver,min(lastt,e[i].w)); 
            if(f>0){ e[i].w-=f,e[i^1].w+=f; return f; }
    } return 0; //没有dfs>0即说明没有增广路,返回0
}

int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,INF); return ans; }

int main(){
    reads(m),reads(n),s=1,t=n;
    int x,y,z; memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        reads(x),reads(y),reads(z),
        add(x,y,z),add(y,x,0); //反边初始流量为0
    } cout<<dinic()<<endl; return 0;
}
p2740 草地排水 //最大流模板题,dinic算法
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

//【p1231】教辅的组成
//给出 书和练习册、书和答案 的对应关系,求同时配成的 书-练习册-答案 的组数。*/

//【分析】源点->练习册->书(拆点)->答案->汇点
//相关编号顺序可以看 https://cdn.luogu.org/upload/pic/13675.png

// Q:为什么书要拆点? A:每本书只能用一次,如果只有一个点,左右可能有多条路径、不唯一。

//1.根据从源点开始的bfs序列,为每一个点分配一个深度;(不会向回流)
//2.进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=1000019;

int s,t,tot=-1,n1,n2,n3,head[N],dep[N]; //s为源点,t为汇点 

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y,int z) //正向边权值为1,反向边权值为0
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot;
   e[++tot].ver=x,e[tot].nextt=head[y],e[tot].w=0,head[y]=tot; }

int bfs(){
    memset(dep,0,sizeof(dep)); //dep记录深度 
    queue<int> q; while(!q.empty()) q.pop();
    dep[s]=1; q.push(s); 
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=e[i].nextt)
            if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
    } if(dep[t]!=0) return 1;
      else return 0; //此时不存在分层图也不存在增广路
}

int dfs(int u,int lastt){ int ans=0; 
    if(u==t) return lastt; //lastt:此点还剩余的流量
    for(int i=head[u];i!=-1&&ans<lastt;i=e[i].nextt) 
        if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
            int f=dfs(e[i].ver,min(lastt-ans,e[i].w)); 
            if(f>0){ e[i].w-=f,e[i^1].w+=f,ans+=f; }
    } if(ans<lastt) dep[u]=-1; return ans;
}

int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,1<<30); return ans; }

int id(int typ,int x){ if(typ==1) return x; if(typ==2) return n2+x;
   if(typ==3) return n2+n1+x; if(typ==4) return n2+n1+n1+x; }

int main(){ memset(head,-1,sizeof(head));
    int m,u,v; reads(n1),reads(n2),reads(n3); //三种物品的数目
    //种类: 1.练习册; 2.书拆点1; 3.书拆点2; 4.答案。
    reads(m); while(m--) reads(u),reads(v),add(id(1,v),id(2,u),1);
    reads(m); while(m--) reads(u),reads(v),add(id(3,u),id(4,v),1);
    for(int i=1;i<=n1;i++) add(id(2,i),id(3,i),1); //书拆点的连边
    s=0,t=n2+n1+n1+n3+1; for(int i=1;i<=n2;i++) add(s,id(1,i),1);
    for(int i=1;i<=n3;i++) add(id(4,i),t,1); //起点&终点的连边
    printf("%d
",dinic()); return 0; //每条路径权值是1,最大流就是组数
}
p1231 教辅的组成 //最大流 + 简单的拆点
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

//【p2936】全流 dinic模板

//1.根据从源点开始的bfs序列,为每一个点分配一个深度;(不会向回流)
//2.进行若干遍dfs寻找增广路,每一次由u推出v必须保证v的深度必须是u的深度+1。

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=1000019;

int s,t,tot=-1,n,head[N],dep[N]; //s为源点,t为汇点 

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y,int z) //正向边权值为1,反向边权值为0
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

int bfs(){
    memset(dep,0,sizeof(dep)); //dep记录深度 
    queue<int> q; while(!q.empty()) q.pop();
    dep[s]=1; q.push(s); 
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=e[i].nextt)
            if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
    } if(dep[t]!=0) return 1;
      else return 0; //此时不存在分层图也不存在增广路
}

int dfs(int u,int lastt){ int ans=0; 
    if(u==t) return lastt; //lastt:此点还剩余的流量
    for(int i=head[u];i!=-1&&ans<lastt;i=e[i].nextt) 
        if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
            int f=dfs(e[i].ver,min(lastt-ans,e[i].w)); 
            if(f>0){ e[i].w-=f,e[i^1].w+=f,ans+=f; }
    } if(ans<lastt) dep[u]=-1; return ans;
}

int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,1<<30); return ans; }

int main(){ 
    memset(head,-1,sizeof(head));
    reads(n); string a,b; s=1,t=26;
    for(int i=1,x,y,z;i<=n;i++){
        cin>>a>>b>>z; x=a[0]-'A'+1,y=b[0]-'A'+1;
        add(x,y,z),add(y,x,0); //正反边
    } cout<<dinic()<<endl; return 0;
}
p2936 全流 //字符输入 + 最大流dinic模板

最小路径覆盖问题

  • 反链:一个点集,任意两个元素都不在同一条链上。
  • 覆盖:所有点都能分布在链上,需要的最小链数。
  • 最小路径覆盖:有向无环图中,用最少多少条简单路径能将所有的点覆盖。
  • 简单路径:就是一条路径不能和其他路径有重复的点,当然也可以认为单个点是一条简单路径)。

根据二分图性质,【最小链覆盖数 = 最长反链长度】【最长链长度 = 最小反链覆盖数】。

(1)二分图匹配算法 //求最大匹配 + 输出匹配方案

  • 【二分图求最小链覆盖数】相当于把每个点拆成两个点,求最大点独立集的大小。
  • 当两边点数相同时(完美匹配),最大点独立集大小=左边点数n-最大匹配数。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;
typedef long long ll;

/*【p2764】最小路径覆盖问题 */

const int N=159;

struct edge{ int ver,nextt; }e[N*N];

int n,m,head[N],tot=0,vis[N],match[N];

void add(int x,int y){ e[++tot].ver=y,e[tot].nextt=head[x],head[x]=tot; }

bool dfs1(int x){ //二分图匹配
    for(int i=head[x];i;i=e[i].nextt){
        if(!vis[e[i].ver]){ vis[e[i].ver]=1;
            if(!match[e[i].ver]||dfs1(match[e[i].ver])){
                match[e[i].ver]=x; return true;
            }
        }
    } return false;
}

void dfs2(int now){ //最小链覆盖的方案
    if(!match[now]){ printf("%d ",now); return; }
    dfs2(match[now]); printf("%d ",now); //↓↓即最小链覆盖的方案
} //相当于将一开始分开的两个点合并起来,按照匹配路径,寻找每条链的链长

int main(){
    int x,y,ans=0; scanf("%d%d",&n,&m);
    
    for(int i=1;i<=m;i++)
        scanf("%d%d",&x,&y),add(x,y);
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if(dfs1(i)) ans++;
    } //↑↑求二分图最大匹配数
    
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++) vis[match[i]]=1; 
    //↑↑用vis数组来标记被右边的点匹配上了的左边点
    for(int i=1;i<=n;i++) //左边点中没有匹配上的就在点独立集中
        if(!vis[i]){ dfs2(i); printf("
"); }
    
    printf("%d
",n-ans); return 0;
}

(2)网络最大流算法 //求最小路径覆盖及方案

  • 附加超级源点S和超级汇点T,建边权为1的图,ans从n开始倒着减,运行最大流。
  • 输出方案,即:从汇点T按残余流量的有无,往前找每条路径,并递归输出。

注意dinic_函数的写法:

void dinic_(){ ans=n; while(bfs()) ans-=dfs(s,1<<30); }

注意求方案的递归函数的写法:

void print(int x){
    if(x<=s) return; //到达起点,输入完毕
    printf("%d ",x); //因为连的每条边都是从i->j+n
    for(int i=head[x];i!=-1;i=e[i].nextt) //所以递归的e[i].ver一定>n
       if(!e[i].w&&e[i].ver<=n*2) print(e[i].ver-n);
}

总代码实现(洛谷 P2764 最小路径覆盖问题):

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

//【p2764】最小路径覆盖问题

//附加超级源点S和超级汇点T,建边权为1的图,运行最大流。

void reads(int &x){ //读入优化(正负整数)
    int f=1;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x*=f; //正负号
}

const int N=1000019;

int s,t,tot=-1,n,m,ans,head[N],dep[N]; //s为源点,t为汇点 

struct node{ int nextt,ver,w; }e[N];

void add(int x,int y,int z) //正向边权值为1,反向边权值为0
 { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }

int bfs(){
    memset(dep,0,sizeof(dep)); //dep记录深度 
    queue<int> q; while(!q.empty()) q.pop();
    dep[s]=1; q.push(s); 
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=e[i].nextt)
            if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
    } if(dep[t]!=0) return 1;
      else return 0; //此时不存在分层图也不存在增广路
}

int dfs(int u,int lastt){ int ans=0; 
    if(u==t) return lastt; //lastt:此点还剩余的流量
    for(int i=head[u];i!=-1&&ans<lastt;i=e[i].nextt) 
        if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
            int f=dfs(e[i].ver,min(lastt-ans,e[i].w)); 
            if(f>0){ e[i].w-=f,e[i^1].w+=f,ans+=f; }
    } if(ans<lastt) dep[u]=-1; return ans;
}

void print(int x){
    if(x<=s) return; //到达起点,输入完毕
    printf("%d ",x); //因为连的每条边都是从i->j+n
    for(int i=head[x];i!=-1;i=e[i].nextt) //所以递归的e[i].ver一定>n
       if(!e[i].w&&e[i].ver<=n*2) print(e[i].ver-n);
}

void dinic_(){ ans=n; while(bfs()) ans-=dfs(s,1<<30); }

int main(){ 
    memset(head,-1,sizeof(head));
    reads(n),reads(m); s=0,t=519;
    for(int i=1;i<=n;i++) //超级源点/汇点的连边
        add(s,i,1),add(i,s,0),add(i+n,t,1),add(t,i+n,0);
    for(int i=1,u,v;i<=m;i++) //拆点
        reads(u),reads(v),add(u,v+n,1),add(v+n,u,0);
    dinic_(); //↓↓从汇点按残余流量的有无,往前找一条路径,并递归输出
    for(int i=head[t];i!=-1;i=e[i].nextt){ //输出方案
        if(e[i].w) continue; //不选还有剩余的
        print(e[i].ver-n),printf("
"); //递归输出
    } printf("%d
",ans); return 0; //最小链覆盖
}

 费用流模型

在网络流图的模型上,每条边增加权值cost,某个可行流的 费用 = 流量 * cost

最小费用最大流(mcmf):在满足流量最大的前提下,找出费用最小的方案。

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#define R register

// 最小费用最大流(mcmf)—— EK算法 + spfa

void reads(ll &x){ //读入优化(正负整数)
    ll f=1;x=0;char S=getchar();
    while(S<'0'||S>'9'){if(S=='-')f=-1;S=getchar();}
    while(S>='0'&&S<='9'){x=x*10+S-'0';S=getchar();}
    x*=f; //正负号
}

const ll N=100019;

struct edge{ ll ver,nextt,flow,cost; }e[2*N];

ll tot=-1,n,m,S,T,maxf=0,minc=0;

ll flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];

void add(ll a,ll b,ll f,ll c)
{ e[++tot].nextt=head[a],head[a]=tot,
  e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 

bool spfa(ll S,ll T){
    queue<ll> q;
    memset(inq,0,sizeof(inq));
    memset(flow,0x7f,sizeof(flow));
    memset(dist,0x7f,sizeof(dist));
    q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
    while(!q.empty()){
        ll x=q.front(); q.pop(); inq[x]=0;
        for(ll i=head[x];i!=-1;i=e[i].nextt){
            if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                dist[e[i].ver]=dist[x]+e[i].cost;
                pre[e[i].ver]=x,lastt[e[i].ver]=i;
                flow[e[i].ver]=min(flow[x],e[i].flow);
                if(!inq[e[i].ver])
                    q.push(e[i].ver),inq[e[i].ver]=1;
            }
        }
    } return pre[T]!=-1;
}

void mcmf(){
    while(spfa(S,T)){
        ll now=T; //↓↓最小费用最大流
        maxf+=flow[T],minc+=dist[T]*flow[T];
        while(now!=S){ //↓↓正边流量-,反边流量+
            e[lastt[now]].flow-=flow[T];
            e[lastt[now]^1].flow+=flow[T]; 
            //↑↑利用xor1“成对储存”的性质
            now=pre[now]; //维护前向边last,前向点pre
        }
    }
}

int main(){
    scanf("%lld%lld%lld%lld",&n,&m,&S,&T);
    memset(head,-1,sizeof(head)); 
    //注意:一定要把head和tot初始化为-1,才能使用xor 1的性质
    for(ll i=1,x,y,f,c;i<=m;i++){
        scanf("%lld%lld%lld%lld",&x,&y,&f,&c);
        add(x,y,f,c),add(y,x,0,-c);
    } mcmf(),printf("%lld %lld
",maxf,minc);
}

 二分图匹配的网络流算法

 最大流最小割问题

原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10332581.html