网络流学习(自用)

Dinic算法 模板

const int maxn = 1e3 + 7;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int from, to, cap, flow;
};
struct Dinic
{
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
    void AddEdge(int from, int to, int cap, int c = 0)
    {
        edges.push_back(Edge{from, to, cap, 0});
        edges.push_back(Edge{to, from, c, 0});
        m = edges.size();
        G[from].push_back(m - 2);//从0开始建边
        G[to].push_back(m - 1);
    }
    bool BFS()//用来建立分层图的
    {
        memset(vis, 0, sizeof(vis));
        queue<int> q;
        q.push(s);
        d[s] = 0;//源点d为0
        vis[s] = 1;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            for (int i = 0; i < G[u].size(); i++){
                Edge &e = edges[G[u][i]];
                if (!vis[e.to] && e.cap > e.flow)//e.cap>e.flow是还有流量的意思,没有被抵消
                {
                    vis[e.to] = 1;
                    d[e.to] = d[u] + 1;//建立分层图
                    q.push(e.to);
                }
            }
        }
        return vis[t];//如果是0,说明流干了,再也到不了汇点了
    }
    int DFS(int u, int dist)
    {
        if (u==t||dist==0)//到了终点
            return dist;
        int flow = 0, f;
        for (int &i = cur[u]; i < G[u].size(); i++){
            Edge &e = edges[G[u][i]];
            if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
            {//d是用来判断分层图是否合法,f是当前这条流和原来这条流选min
                e.flow += f; //这条边的流过的流量+f
                edges[G[u][i]^1].flow -= f; //反边的流量-f
                flow += f;  //已有流量
                dist -= f;
                if (!dist)//这条路径的流量是无穷的 
                    break;
            }
        }
        return flow;
    }
    int Maxflow(int s, int t)
    {
        this->s = s;
        this->t = t;
        int flow = 0;
        while (BFS()){//建立个分层图
            memset(cur, 0, sizeof(cur));
            flow += DFS(s, INF);
        }
        return flow;
    }
};

二分图匹配问题

建图:

左子集的点与源点相连,右子集的点与汇点相连

所有的边权都是1

匈牙利算法

#include<bits/stdc++.h>
using namespace std;
#define N 2010
int n,m,e,ans;
int vis[N][N];
int ask[N],matched[N];//match是匹配了谁
inline bool found(int x){ //dfs找增广路
    for (int i=m+1;i<=n;i++)
      if (vis[x][i]){
        if (ask[i]) continue;
        ask[i] = 1;
        if (!matched[i] || found(matched[i])) {
			matched[i] = x ;
			return true;
		}
      }
    return false;
}
inline void match(){
    int cnt = 0;//cnt是计数器
    memset(matched,0,sizeof(matched));
    for (int i = 1;i<=m; i++){
      memset(ask,0,sizeof(ask));
      if (found(i)) cnt++;	//找到了就加1
    }
    ans = cnt;
}
int main()
{
    cin>>m>>n;
    while(1){
        int u,v;cin>>u>>v;
        if(u==-1&&v==-1)break;
        vis[u][v]=1;
    }
    match();
    cout<<ans<<'\n';
    for(int i=1;i<=n;i++){
        if(matched[i])
        cout<<matched[i]<<" "<<i<<endl;
    }
    return 0;
}

Dinic最大流

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 5e3 + 7;
const int INF = 0x3f3f3f3f;
int T;
struct Edge
{
    int from, to, cap, flow;
};
struct Dinic
{
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
    void AddEdge(int from, int to, int cap, int c = 0)
    {
        edges.push_back(Edge{from, to, cap, 0});
        edges.push_back(Edge{to, from, c, 0});
        m = edges.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }
    bool BFS()
    {
        memset(vis, 0, sizeof(vis));
        queue<int> q;
        q.push(s);
        d[s] = 0;
        vis[s] = 1;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            for (int i = 0; i < G[u].size(); i++)
            {
                Edge &e = edges[G[u][i]];
                if (!vis[e.to] && e.cap > e.flow)
                {
                    vis[e.to] = 1;
                    d[e.to] = d[u] + 1;
                    q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int u, int dist)
    {
        if (u == t || dist == 0)
            return dist;
        int flow = 0, f;
        for (int &i = cur[u]; i < G[u].size(); i++)
        {
            Edge &e = edges[G[u][i]];
            if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
            {
                e.flow += f;
                edges[G[u][i] ^ 1].flow -= f;
                flow += f;
                dist -= f;
                if (!dist)
                    break;
            }
        }
        return flow;
    }
    int Maxflow(int s, int t){
        this->s = s;
        this->t = t;
        int flow = 0;
        while (BFS()){
            memset(cur, 0, sizeof(cur));
            flow += DFS(s, INF);
        }
        cout<<flow<<endl;
        for(auto mm: edges){
            if(mm.from&&mm.to!=T)
                if(mm.flow>0)
                 cout<<mm.from<<" "<<mm.to<<endl;

        }
        return flow;
    }
};
signed main()
{
    Dinic D;
    int n,m;cin>>m>>n;
    for(int i=1;i<=m;i++){
        D.AddEdge(0,i,1);
    }
    for(int i=m+1;i<=n;i++){
        D.AddEdge(i,n+1,1);
    }
    T=n+1;
    while(1){
        int u,v;cin>>u>>v;
        if(u==-1&&v==-1)break;
        D.AddEdge(u,v,1);
    }
    D.Maxflow(0,n+1);
    return 0;
}

最大权闭合子图 

建图:

正点权与源点相连,边权为点权;负点权与汇点相连,边权为点权的绝对值

正点权与负点权相连的部分为正无穷

输出:

输出用vis,只要vis为1,说明在最后一次建图的时候有参与

所以权值为正的点和权值为负的点按照同样的方法就能输出了

参考: 最大权闭合子图  

结论:最大权闭合子图的权值等于所有正权点之和减去最小割

关于建图:

与源点相连的是点权为正的,与汇点相连的点权是负的,首先先把正的全加起来,然后再去算减去的最小割。如果有一条边正的点权比负的点权的绝对值小,那么sum减去最小割后,就相当于这条边没选过。

P2762 太空飞行计划问题 - 洛谷 

关于输出:

vis表示这一条边一定被走过,所以输出vis

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 5e3 + 7;
const int INF = 0x3f3f3f3f;
int T;
struct Edge{
    int from, to, cap, flow;
};
struct Dinic
{
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
    void AddEdge(int from, int to, int cap, int c = 0){
        edges.push_back(Edge{from, to, cap, 0});
        edges.push_back(Edge{to, from, c, 0});
        m = edges.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }
    bool BFS(){
        memset(vis, 0, sizeof(vis));
        queue<int> q;
        q.push(s);
        d[s]=0;
        vis[s]=1;
        while (!q.empty()){
            int u = q.front();
            q.pop();
            for (int i = 0; i < G[u].size(); i++){
                Edge &e = edges[G[u][i]];
                if (!vis[e.to]&&e.cap> e.flow){
                    vis[e.to]=1;
                    d[e.to]=d[u]+1;
                    q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int u, int dist){
        if (u == t || dist == 0)
            return dist;
        int flow = 0, f;
        for (int &i=cur[u];i<G[u].size(); i++){
            Edge &e = edges[G[u][i]];
            if (d[u]+1==d[e.to]&&(f=DFS(e.to,min(dist,e.cap - e.flow))) > 0){
                e.flow += f;
                edges[G[u][i]^1].flow -= f;
                flow+=f;
                dist-=f;
                if (!dist)break;
            }
        }
        return flow;
    }
    int Maxflow(int s, int t,int n,int m){
        this->s = s;
        this->t = t;
        int flow = 0;
        while (BFS()){
            memset(cur,0,sizeof(cur));
            flow += DFS(s,INF);
        }
        set<int> ss;
            for(int i=1;i<=n;i++)
               if(vis[i]) cout<<i<<' ';
               cout<<endl;
            for(int i=1;i<=m;i++)
               if(vis[i+n]) cout<<i<<' ';
        cout<<'\n';
        return flow;
    }
};
int a[110];
signed main()
{
    Dinic D;
    int n,m,sum = 0;
    cin>>n>>m;
    int s=0,t=n+m+1;
    for(int i=1;i<=n;i++){
        int x;cin >> x;
        D.AddEdge(s,i,x);
        sum+=x;
        char tools[10000];
        memset(tools,0,sizeof(tools));
        cin.getline(tools,10000);
        int ulen=0,tool;
        while (sscanf(tools + ulen, "%d", &tool) == 1){
            D.AddEdge(i,tool+n,INF);//中间的部分
            if (tool == 0) ulen++;
            else {
                while (tool) {
                    tool /= 10;
                    ulen++;
                }
            }
            ulen++;
        }
    }
    for (int i=1;i<=m;i++){
        int x;cin>>x;
        D.AddEdge(i+n,t,x);
    }
    int ans=D.Maxflow(s,t,n,m);
    printf("%d\n", sum-ans);
    return 0;
}

有向无环图最小路径覆盖(无交点)

P2764 最小路径覆盖问题

参考:最小路径覆盖问题(网络流24题) - Brave_Cattle - 博客园 (cnblogs.com) 

建图:

同二分图匹配的建图方式,但右子集中的点序号是自己+n。

输出:

同二分图匹配的输出方式 ,但注意序号

定理:

最小路径覆盖数=|G|-二分图最大匹配数(|G|是有向图中的总点数)

首先我们知道,在二分图中一个点就代表者一条路径。那么如果此时二分图内没有连边,这个公式是成立的。每当二分图内增加一条边,最大匹配数就会+1,而一条匹配边会连接二分图中的两个点,那么两个点间本来有两条路径覆盖,就变成了一条。同理,每加入一条边匹配数就会+1,路径覆盖数就会-1。所以这个公式是成立的。但是这个公式是对二分图适用的,如何将它转化到这个问题上来呢?

这时我们先将一个点拆A成出点Ax和入点Ay,那么在连有向边A -> B的时候就将Ax连向By。就将有向图变成了一个二分图。

匈牙利算法 

#include<bits/stdc++.h>
using namespace std;
#define N 601
int n,m,e,ans;//n是左边的人数,m是右边的人数,e是边数,ans是匹配数
int vis[N][N];
int ask[N],matched[N];//match是匹配了谁
inline bool found(int x){ //dfs找增广路
    for (int i = n+1 ; i<=n+n ; i++){
      if (vis[x][i]){
        if (ask[i]) continue;
        ask[i] = 1;
        if (!matched[i] || found(matched[i])) {
			matched[i] = x ;
			return true;
		}
      }
    }
    return false;
}
inline void match(){
    int cnt = 0;//cnt是计数器
    memset(matched,0,sizeof(matched));
    for (int i = 1 ; i <= n ; i++){
      memset(ask,0,sizeof(ask));
      if (found(i)) cnt++;	//找到了就加1
    }
    ans = cnt;
}
bool print[301];
void P(int x){
    if(x==0)return ;
    P(matched[x+n]);
    cout<<x<<' ';
    print[x]=1;
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        vis[u][v+n]=1;
    }
    match();
    for(int i=2*n;i>n;i--){
        if(!print[i-n]){
            P(i-n);cout<<endl;
        }
    }
    cout<<n-ans;
    return 0;
}

Dinic算法

#include<bits/stdc++.h>
using namespace std;
#define N 601
int matched[N];
const int maxn = 1e3 + 7;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int from, to, cap, flow;
};
struct Dinic
{
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool vis[maxn];
    int d[maxn];
    int cur[maxn];
    void AddEdge(int from, int to, int cap, int c = 0)
    {
        edges.push_back(Edge{from, to, cap, 0});
        edges.push_back(Edge{to, from, c, 0});
        m = edges.size();
        G[from].push_back(m - 2);//从0开始建边
        G[to].push_back(m - 1);
    }
    bool BFS()
    {
        memset(vis, 0, sizeof(vis));
        queue<int> q;
        q.push(s);
        d[s] = 0;
        vis[s] = 1;
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            for (int i = 0; i < G[u].size(); i++)
            {
                Edge &e = edges[G[u][i]];
                if (!vis[e.to] && e.cap > e.flow){
                    vis[e.to] = 1;
                    d[e.to] = d[u] + 1;
                    q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int u, int dist)
    {
        if (u == t || dist == 0)
            return dist;
        int flow = 0, f;
        for (int &i = cur[u]; i < G[u].size(); i++)
        {
            Edge &e = edges[G[u][i]];
            if (d[u] + 1 == d[e.to] && (f = DFS(e.to, min(dist, e.cap - e.flow))) > 0)
            {
                e.flow += f; //这条边的流过的流量+f
                edges[G[u][i]^1].flow -= f; //反边的流量-f
                flow += f;  //已有流量
                dist -= f;
                if (!dist)//这条路径的流量是无穷的
                    break;
            }
        }
        return flow;
    }
    int Maxflow(int s, int t,int n)
    {
        this->s = s;
        this->t = t;
        int flow = 0;
        while (BFS()){
            memset(cur, 0, sizeof(cur));
            flow += DFS(s, INF);
        }
         for(auto mm: edges){
            if(mm.from&&mm.to!=t)
                if(mm.flow>0){
                    matched[mm.to]=mm.from;
                    //cout<<mm.from<<" "<<mm.to<<endl;
                }
        }
        return flow;
    }
};
bool print[301];
void P(int x,int n){
    if(x==0)return ;
    P(matched[x+n],n);
    cout<<x<<' ';
    print[x]=1;
}
int main()
{
    Dinic D;
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++){
        D.AddEdge(0,i,1);
    }
    for(int i=1+n;i<=2*n;i++){
        D.AddEdge(i,2*n+1,1);
    }
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        D.AddEdge(u,v+n,1);
    }
    int ans=D.Maxflow(0,2*n+1,n);
    for(int i=2*n;i>n;i--){
        if(!print[i-n]){
            P(i-n,n);
            cout<<endl;
        }
    }
    cout<<n-ans;
    return 0;
}

二分图多重匹配

P2763 试题库问题 

建图:

因为一道题只可以有一个,所以源点和试题的边的容量为1

同理一道题只能满足一种类型,所以试题和类型的边的容量也为1

而需要满足的类型是有多个的,所以类型与汇点的边的容量为所需类型的数量

猜你喜欢

转载自blog.csdn.net/zy98zy998/article/details/127739571