网络流

想看定义和证明可以到算法学习笔记(28): 网络流 - 知乎 

最大流

EK算法

时间复杂度O(nm^{2}) 较慢

1.残留网络的可行流+原图的可行流=原题的另一个可行流

2.可行流的残留网络中不存在增广路,那么就找到了最大流

例题 求一个网络的最大流
EK算法时间复杂度为O(点数*边数^2),但是这个复杂度非常玄学,上界非常宽松
基本可以解决点数+边数<10000的问题

from collections import defaultdict, deque

class EK:

    def __init__(self, n, m, s, t):
        self.flow = [0] * (n + 10)
        self.pre = [0] * (n + 10)
        self.used = set()
        self.g = defaultdict(list)
        self.edges_val = defaultdict(int)
        self.m = m
        self.s = s
        self.t = t
        self.res = 0

    def addEdge(self, From, to, flow):
        self.edges_val[(From, to)] += flow
        self.edges_val[(to, From)] += 0
        self.g[From].append(to)
        self.g[to].append(From)

    def bfs(self) -> bool:
        self.used.clear()
        q = deque()
        q.append(self.s)
        self.used.add(self.s)
        self.flow[self.s] = float('inf')
        while q:
            now = q.popleft()
            for nxt in self.g[now]:
                edge = (now, nxt)
                val = self.edges_val[edge]
                if nxt not in self.used and val:
                    self.used.add(nxt)
                    self.flow[nxt] = min(self.flow[now], val)
                    self.pre[nxt] = now
                    if nxt == self.t:
                        return True
                    q.append(nxt)
        return False

    def EK(self) -> int:
        while self.bfs():
            self.res += self.flow[self.t]
            From = self.t
            to = self.pre[From]
            while True:
                edge = (From, to)
                reverse_edge = (to, From)
                self.edges_val[edge] += self.flow[self.t]
                self.edges_val[reverse_edge] -= self.flow[self.t]
                if to == self.s:
                    break
                From = to
                to = self.pre[From]
        return self.res

n, m, s, t = map(int, input().split())
ek = EK(n, m, s, t)
for _ in range(m):
    a, b, c = map(int, input().split())
    ek.addEdge(a, b, c)
print(ek.EK())

Dinic算法 

时间复杂度O(n^{2}m) 较快,此算法为求最大流最常用算法

// from : https://oi-wiki.org/graph/flow/max-flow/#dinic
#define maxn 250
#define INF 0x3f3f3f3f

struct Edge {
    int from, to, cap, flow;

    Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};

struct Dinic {
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[maxn];
    int d[maxn], cur[maxn];
    bool vis[maxn];

    void init(int n) {
        for (int i = 0; i < n; i++) G[i].clear();
        edges.clear();
    }

    void AddEdge(int from, int to, int cap) {
        edges.push_back(Edge(from, to, cap, 0));
        edges.push_back(Edge(to, from, 0, 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 x = Q.front();
            Q.pop();
            for (int i = 0; i < G[x].size(); i++) {
                Edge& e = edges[G[x][i]];
                if (!vis[e.to] && e.cap > e.flow) {
                    vis[e.to] = 1;
                    d[e.to] = d[x] + 1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }

    int DFS(int x, int a) {
        if (x == t || a == 0) return a;
        int flow = 0, f;
        for (int& i = cur[x]; i < G[x].size(); i++) {
            Edge& e = edges[G[x][i]];
            if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
                e.flow += f;
                edges[G[x][i] ^ 1].flow -= f;
                flow += f;
                a -= f;
                if (a == 0) 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;
    }
};

Dinic算法 - 解决男女配对问题

  1. 创建一个超级源点,一个超级汇点
  2. 源点流向所有男生,所有女生流向汇点,容量为1
  3. 男生流向自己喜欢的女生,容量为1
  4. 这个网络的最大流就是最大配对
// https://blog.csdn.net/nreyong/article/details/122931638?spm=1001.2014.3001.5501
class Solution {
public:
    #define maxn 505
    #define INF 0x3f3f3f3f

    struct Edge {
        int from, to, cap, flow;

        Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
    };

    struct Dinic {
        int n, m, s, t;
        vector<Edge> edges;
        vector<int> G[maxn];
        int d[maxn], cur[maxn];
        bool vis[maxn];

        void init(int n) {
            for (int i = 0; i < n; i++) G[i].clear();
            edges.clear();
        }

        void AddEdge(int from, int to, int cap) {
            edges.push_back(Edge(from, to, cap, 0));
            edges.push_back(Edge(to, from, 0, 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 x = Q.front();
            Q.pop();
            for (int i = 0; i < G[x].size(); i++) {
                Edge& e = edges[G[x][i]];
                if (!vis[e.to] && e.cap > e.flow) {
                vis[e.to] = 1;
                d[e.to] = d[x] + 1;
                Q.push(e.to);
                }
            }
            }
            return vis[t];
        }

        int DFS(int x, int a) {
            if (x == t || a == 0) return a;
            int flow = 0, f;
            for (int& i = cur[x]; i < G[x].size(); i++) {
            Edge& e = edges[G[x][i]];
            if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
                e.flow += f;
                edges[G[x][i] ^ 1].flow -= f;
                flow += f;
                a -= f;
                if (a == 0) 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;
        }
    };
    int maximumInvitations(vector<vector<int>>& grid) {
        Dinic D;
        int n = grid.size(), m = grid[0].size();
        D.n = n + m + 10;
        D.init(D.n);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j]) {
                    D.AddEdge(i, n + j, 1);
                }
            }
        }
        for (int i = 0; i < grid.size(); i++) {
            D.AddEdge(500, i, 1);
        }
        for (int i = 0; i < grid[0].size(); i++) {
            D.AddEdge(n + i, 501, 1);
        }
        D.s = 500, D.t = 501;
        return D.Maxflow(D.s, D.t);
    }
};

最小费用最大流

例题:【模板】最小费用最大流

// from : https://oi-wiki.org/graph/flow/min-cost/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>

#define N 5005
#define M 100005
const int INF = 0x3f3f3f3f;
int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], min_cost;
bool vis[N];
 
void add(int u, int v, int w, int c) {
    ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
 
void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
 
bool spfa(int s, int t) {
    memset(dis, 0x3f, sizeof(dis));
    memcpy(cur, lnk, sizeof(lnk));
    std::queue<int> q;
    q.push(s), dis[s] = 0, vis[s] = 1;
    while (!q.empty()) {
        int u = q.front();
        q.pop(), vis[u] = 0;
        for (int i = lnk[u]; i; i = nxt[i]) {
            int v = ter[i];
            if (cap[i] && dis[v] > dis[u] + cost[i]) {
                dis[v] = dis[u] + cost[i];
                if (!vis[v]) q.push(v), vis[v] = 1;
            }
        }
    }
    return dis[t] != INF;
}
 
int dfs(int u, int t, int flow) {
    if (u == t) return flow;
    vis[u] = 1;
    int ans = 0;
    for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
        int v = ter[i];
        if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
            int x = dfs(v, t, std::min(cap[i], flow - ans));
            if (x) min_cost += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
        }
    }
    vis[u] = 0;
    return ans;
}
 
int mcmf(int s, int t) {
    int ans = 0;
    while (spfa(s, t)) {
        int x;
        while ((x = dfs(s, t, INF))) ans += x;
    }
    return ans;
}
 
int main() {
    int s, t;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--) {
        int u, v, w, c;
        scanf("%d%d%d%d", &u, &v, &w, &c);
        addedge(u, v, w, c);
    }
    int max_flow = mcmf(s, t);
    printf("%d %d\n", max_flow, min_cost);
    return 0;
}

例题:两个数组最小的异或值之和 

建图:

  • 从源点连容量为1,费用为0的边到nums1的每个数字
  • 从nums2的每个数字连容量为1,费用为0的边到汇点
  • 从每个数字nums1[i]向每个nums2[j]连容量为1,费用为nums1[i]^nums2[j]的边
  • 此时最大流就是nums1中数字个数,最小费用就是两个数组最小的异或值之和
class Solution {
public:
    #define N 5005
    #define M 100005
    const int INF = 0x3f3f3f3f;
    int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], min_cost;
    bool vis[N];
    
    void add(int u, int v, int w, int c) {
        ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
    }
    
    void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
    
    bool spfa(int s, int t) {
        memset(dis, 0x3f, sizeof(dis));
        memcpy(cur, lnk, sizeof(lnk));
        std::queue<int> q;
        q.push(s), dis[s] = 0, vis[s] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop(), vis[u] = 0;
            for (int i = lnk[u]; i; i = nxt[i]) {
                int v = ter[i];
                if (cap[i] && dis[v] > dis[u] + cost[i]) {
                    dis[v] = dis[u] + cost[i];
                    if (!vis[v]) q.push(v), vis[v] = 1;
                }
            }
        }
        return dis[t] != INF;
    }
    
    int dfs(int u, int t, int flow) {
        if (u == t) return flow;
        vis[u] = 1;
        int ans = 0;
        for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
            int v = ter[i];
            if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
                int x = dfs(v, t, std::min(cap[i], flow - ans));
                if (x) min_cost += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
            }
        }
        vis[u] = 0;
        return ans;
    }
    
    int mcmf(int s, int t) {
        int ans = 0;
        while (spfa(s, t)) {
            int x;
            while ((x = dfs(s, t, INF))) ans += x;
        }
        return ans;
    }

    int minimumXORSum(vector<int>& nums1, vector<int>& nums2) {
        for (int i = 0; i < nums1.size(); i++) {
            for (int j = 0; j < nums2.size(); j++) {
                addedge(i, 500 + j, 1, nums1[i] ^ nums2[j]);
            }
        }
        for (int i = 0; i < nums1.size(); i++) {
            addedge(1000, i, 1, 0);
        }
        for (int i = 0; i < nums2.size(); i++) {
            addedge(500 + i, 1001, 1, 0);
        }
        int max_flow = mcmf(1000, 1001);
        return min_cost;
    }
};

例题: 数组的最大与和

建图:

  • 从源点连容量为1, 费用为0的边到每一个数字
  • 从每个篮子连容量为2, 费用为0的边到汇点
  • 从每个数字nums[i]向每个篮子j连容量为+\infty, 费用为-(nums[i]&j), 取负号是因为我们想求最大费用最大流, 而模板是求最小费用最大流, 所以取负号套模板后, 将答案取相反数就是最大费用
  • 此时的最大流就是进入篮子的数字的个数, 最大费用就是这个过程中产生的最大与和
#define N 5000
#define M 100000
class Solution {
public:
    int INF = 0x3f3f3f3f;
    int n, m, tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], ret;
    bool vis[N];
    
    void add(int u, int v, int w, int c) {
        ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
    }
    
    void addedge(int u, int v, int w, int c) { add(u, v, w, c), add(v, u, 0, -c); }
    
    bool spfa(int s, int t) {
        memset(dis, 0x3f, sizeof(dis));
        memcpy(cur, lnk, sizeof(lnk));
        std::queue<int> q;
        q.push(s), dis[s] = 0, vis[s] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop(), vis[u] = 0;
            for (int i = lnk[u]; i; i = nxt[i]) {
                int v = ter[i];
                if (cap[i] && dis[v] > dis[u] + cost[i]) {
                    dis[v] = dis[u] + cost[i];
                    if (!vis[v]) q.push(v), vis[v] = 1;
                }
            }
        }
        return dis[t] != INF;
    }
    
    int dfs(int u, int t, int flow) {
        if (u == t) return flow;
        vis[u] = 1;
        int ans = 0;
        for (int &i = cur[u]; i && ans < flow; i = nxt[i]) {
            int v = ter[i];
            if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i]) {
                int x = dfs(v, t, std::min(cap[i], flow - ans));
                if (x) ret += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
            }
        }
        vis[u] = 0;
        return ans;
    }
    
    int mcmf(int s, int t) {
        int ans = 0;
        while (spfa(s, t)) {
            int x;
            while ((x = dfs(s, t, INF))) ans += x;
        }
        return ans;
    }
    int maximumANDSum(vector<int>& nums, int n) {
        int s = 1001, t = 1002;
        for (int i = 0; i < nums.size(); i++) {
            addedge(s, i, 1, 0);
        }
        for (int i = 0; i < n; i++) {
            addedge(500 + i + 1, t, 2, 0);
        }
        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j < n; j++) {
                addedge(i, 500 + j + 1, 100000, -(nums[i] & (j + 1)));
            }
        }
        int ans = mcmf(s, t);
        return -ret;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值