网络流初步

前言

由于图论学习总结内容过多,全放在一篇博客过于冗长现进行拆分,本文是网络流初步部分,其他部分地址见:图论学习总结(For XCPC)
注:在本篇博客中,我并没有介绍求最大流和费用流的方法,主要聚焦于一些常用的网络流建模方法,同时对于问题的建模也是解决网络流问题的核心。

五、网络流初步

  • 网络流的核心在于建图。建图是精髓也是难点。
  • 网络流的建图方法一定程度上刻画了贪心问题的内在性质,从而简便地支持了反悔,不需要我们为每道贪心问题都寻找反悔策略。

最大流(Maximum flow,简称 M F MF MF

e g 1 : eg1: eg1: P2764【网络流24题】最小路径覆盖问题 - 洛谷

题目大意
给定有向图 G = ( V , E ) G=(V,E) G=(V,E) 。设 P P P G G G 的一个简单路(顶点不相交)的集合。如果 V V V 中每个定点恰好在 P P P 的一条路上,则称 P P P G G G 的一个路径覆盖。 P P P 中路径可以从 V V V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 0 0 G G G 的最小路径覆盖是 G G G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 DAG(有向无环图) G G G 的最小路径覆盖。
输入格式
第一行有两个正整数 n n n m m m n n n 是给定 DAG(有向无环图) G G G 的顶点数, m m m G G G 的边数。接下来的 m m m 行,每行有两个正整数 i i i j j j 表示一条有向边 ( i , j ) (i,j) (i,j)
输出格式
从第一行开始,每行输出一条路径。文件的最后一行是最少路径数。
提示
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 150 1\leq n\leq 150 1n150 1 ≤ m ≤ 6000 1\leq m\leq 6000 1m6000
由 @FlierKing 提供 SPJ
题目解析

  • 在最小路径覆盖问题中,每个点只能有一个前驱和一个后继。在这种情况下,我们可以把一个点拆成一个入点和一个出点。 a a a b b b 有边则 a 出 a出 a b 入 b入 b,容量为 1。
  • S向出点点集连容量为1的边,入点点集向T连边,若a出到b入流过了1的流量,那么在路径覆盖中b在a的后面,b不是路径的起点。
  • 若S到T的最大流为x,则有x个点不是路径覆盖的起点,但剩下的 n − x n-x nx个点一定是起点,所以 a n s = n − x ans=n-x ans=nx

启发:对于限制了点的出入度都为1的时候,我们可以将一个点拆成一个入点和一个出点

ac代码参考

import sys
from types import GeneratorType
from math import inf
from collections import deque

def bootstrap(f, stack=[]):
    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to
    return wrappedfunc

input = lambda : sys.stdin.readline().rstrip("\r\n")
out = sys.stdout.write
def S():
    return input()
def I():
    return int(input())
def MI():
    return map(int, input().split())
def MF():
    return map(float, input().split())
def MS():
    input().split()
def LS():
    return list(input().split())
def LI():
    return list(MI())
def print1(x):
    return out(str(x)+"\n")
def print2(x,y):
    return out(str(x)+" "+str(y)+"\n")
def print3(x,y,z):
    return out(str(x) + " " + str(y) + " " + str(z) + "\n")
def Query(i,j):
    print3('?',i,j)
    sys.stdout.flush()
    qi = I()
    return qi
def print_arr(arr):
    out(' '.join(map(str,arr)))
    out(str("\n"))
#---------------------------------#----------------------------#
# Dinic算法求最大流
# 用法示例
# 创建一个 Maxflow 实例
# mf = Maxflow(100)  # n 是节点数
# 添加边,例如:
# mf.add_edge(u, v, cap)
# 计算最大流
# max_flow = mf.max_flow(source, sink)
class Maxflow:
    # n 节点数, 从0开始编号
    def __init__(self, n):
        self.n = n
        self.G = [[] for _ in range(n)]
        self.depth = [0] * n
        # cur[u] 表示当前弧下标
        self.cur = [0] * n

    # 添加一条从 u 到 v 的容量为 cap 的边
    def add_edge(self, u, v, cap):
        # 正向边 u -> v
        self.G[u].append([v, cap, len(self.G[v])])  # 分别存(该边的终点,容量,反向边的idx)
        # 反向边 v -> u,初始容量为 0
        self.G[v].append([u, 0, len(self.G[u]) - 1])  # 分别存(该边的终点,容量,正向边的idx)

    # 从源点出发进行一次bfs,计算出每个点的深度
    # 对当前残差图的BFS构造一个level图(或为顶点分配深度), 并检查是否更多的流量是可能的。
    def bfs(self, s, t):
        self.depth = [-1] * self.n
        self.depth[s] = 0
        q = deque([s])
        while q:
            u = q.popleft()
            for v, cap, _ in self.G[u]:
                if self.depth[v] == -1 and cap > 0:
                    self.depth[v] = self.depth[u] + 1
                    q.append(v)
        return self.depth[t] != -1

    # 从 u 出发,沿着深度优先搜索的路径,不断增广
    def dfs(self, u, t, f=inf):
        if u == t:
            return f
        max_flow = 0
        while self.cur[u] < len(self.G[u]):
            v, cap, rev = self.G[u][self.cur[u]]
            # 只有当 u 的深度加 1 之后等于 v 的深度时,我们才让他流 [不能走回头路]
            if cap > 0 and self.depth[v] == self.depth[u] + 1:
                # 流多少呢,能装多少流就流多少
                df = self.dfs(v, t, min(f - max_flow, cap))
                if df > 0:
                    self.G[u][self.cur[u]][1] -= df
                    self.G[v][rev][1] += df
                    max_flow += df
                    if max_flow == f:
                        break
                else:
                    self.depth[v] = 0
            # 只有当前面的边都已经流满/不可达,我们才更新 cur[u],下一次直接从第cur[u]条边开始枚举
            self.cur[u] += 1
        return max_flow

    # s 是源点,t 是汇点
    def max_flow(self, s, t):
        total_flow = 0
        # 当找不到增广路时,bfs 返回 false
        while self.bfs(s, t):
            self.cur = [0] * self.n
            flow = self.dfs(s, t)
            while flow > 0:
                total_flow += flow
                flow = self.dfs(s, t)
        return total_flow

    # 用手写栈优化dfs
    @bootstrap
    def dfs_bootstrap(self, u, t, f=inf):
        if u == t:
            yield f
        max_flow = 0
        while self.cur[u] < len(self.G[u]):
            v, cap, rev = self.G[u][self.cur[u]]
            # 只有当 u 的深度加 1 之后等于 v 的深度时,我们才让他流 [不能走回头路]
            if cap > 0 and self.depth[v] == self.depth[u] + 1:
                # 流多少呢,能装多少流就流多少
                df = yield self.dfs_bootstrap(v, t, min(f - max_flow, cap))
                if df > 0:
                    self.G[u][self.cur[u]][1] -= df
                    self.G[v][rev][1] += df
                    max_flow += df
                    if max_flow == f:
                        break
                else: self.depth[v] = 0
            # 只有当前面的边都已经流满/不可达,我们才更新 cur[u],下一次直接从第cur[u]条边开始枚举
            self.cur[u] += 1
        yield max_flow

    def max_flow_bootstrap(self, s, t):
        total_flow = 0
        # 当找不到增广路时,bfs 返回 false
        while self.bfs(s, t):
            self.cur = [0] * self.n
            flow = self.dfs_bootstrap(s, t)
            while flow > 0:
                total_flow += flow
                flow = self.dfs_bootstrap(s, t)
        return total_flow

def solve():
    def path(x,flag):
        if flag: res.insert(0,x+1)
        else: res.append(x+1)
        vis[x] = 1
        for y,cap,rev in mf.G[x]:
            if y == s or y == t: continue
            y = y-n
            if not cap and not vis[y]: path(y,0)
        for y,cap,rev in mf.G[x+n]:
            if y == s or y == t: continue
            if cap and not vis[y]: path(y,1)


    n,m = MI()
    mf = Maxflow(n+n+2)
    s,t = n+n,n+n+1
    for _ in range(m):
        x,y = MI()
        mf.add_edge(x-1,n+y-1,1)
    for i in range(n):
        mf.add_edge(s,i,1)
        mf.add_edge(i+n,t,1)
    ans = n-mf.max_flow_bootstrap(s,t)
    vis = [0]*(n+1)
    for i in range(n):
        if not vis[i]:
            res = []
            path(i,0)
            print_arr(res)

    print1(ans)

solve()
e g 2 : eg2: eg2 P2765【网络流24题】魔术球问题 - 洛谷

题目大意
假设有 n n n 根柱子,现要按下述规则在这 n n n 根柱子中依次放入编号为 1 1 1 2 2 2 3 3 3,…的球

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 2 2 2 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 n n n 根柱子上最多能放多少个球。例如,在 4 4 4 根柱子上最多可放 11 11 11 个球。

对于给定的 n n n,计算在 n n n 根柱子上最多能放多少个球。

输入格式

只有一行一个整数 n n n,代表柱子数。

输出格式

本题存在 Special Judge。
请将 n n n 根柱子上最多能放的球数以及相应的放置方案输出。
输出的第一行是球数。

接下来的 n n n 行,每行若干个整数,代表一根柱子上的球的编号,数字间用单个空格隔开。

数据规模与约定

对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 55 1 \le n \le 55 1n55

题目解析
本题如果把能相邻的球连边,就是最小路径覆盖问题。但本题求的是 n n n 条路径最多能容纳多少个点。在本题中,如果一个球可放在多根柱子上,通过后面的寻找增广路的过程也可以反悔(退流),以贪心的求得最优解。
所以我们可以一个点一个点的增加,如果加了一个点流量+1,则不用新增柱子(其可以放到一个另一个球的上面),否则加一根柱子,直到柱子加到n。
每加入一个数,最大流要么不变,要么+1

  • MF+1:之前某个无后继的出点流了1流量到该新点(不增加柱子)。
  • MF不变:新点接不到之前任何一个点后面(放不到任何一个柱子上)。

ac代码参考:

import sys
from types import GeneratorType
from math import inf,sqrt
from collections import deque


def bootstrap(f, stack=[]):
    def wrappedfunc(*args, **kwargs):
        if stack:
            return f(*args, **kwargs)
        else:
            to = f(*args, **kwargs)
            while True:
                if type(to) is GeneratorType:
                    stack.append(to)
                    to = next(to)
                else:
                    stack.pop()
                    if not stack:
                        break
                    to = stack[-1].send(to)
            return to
    return wrappedfunc

input = lambda : sys.stdin.readline().rstrip("\r\n")
out = sys.stdout.write
def S():
    return input()
def I():
    return int(input())
def MI():
    return map(int, input().split())
def MF():
    return map(float, input().split())
def MS():
    input().split()
def LS():
    return list(input().split())
def LI():
    return list(MI())
def print1(x):
    return out(str(x)+"\n")
def print2(x,y):
    return out(str(x)+" "+str(y)+"\n")
def print3(x,y,z):
    return out(str(x) + " " + str(y) + " " + str(z) + "\n")
def Query(i,j):
    print3('?',i,j)
    sys.stdout.flush()
    qi = I()
    return qi
def print_arr(arr):
    out(' '.join(map(str,arr)))
    out(str("\n"))
#---------------------------------#----------------------------#
# Dinic算法求最大流
# 用法示例
# 创建一个 Maxflow 实例
# mf = Maxflow(100)  # n 是节点数
# 添加边,例如:
# mf.add_edge(u, v, cap)
# 计算最大流
# max_flow = mf.max_flow(source, sink)
class Maxflow:
    # n 节点数, 从0开始编号
    def __init__(self, n):
        self.n = n
        self.G = [[] for _ in range(n)]
        self.depth = [0] * n
        # cur[u] 表示当前弧下标
        self.cur = [0] * n

    # 添加一条从 u 到 v 的容量为 cap 的边
    def add_edge(self, u, v, cap):
        # 正向边 u -> v
        self.G[u].append([v, cap, len(self.G[v])])  # 分别存(该边的终点,容量,反向边的idx)
        # 反向边 v -> u,初始容量为 0
        self.G[v].append([u, 0, len(self.G[u]) - 1])  # 分别存(该边的终点,容量,正向边的idx)

    # 从源点出发进行一次bfs,计算出每个点的深度
    # 对当前残差图的BFS构造一个level图(或为顶点分配深度), 并检查是否更多的流量是可能的。
    def bfs(self, s, t):
        self.depth = [-1] * self.n
        self.depth[s] = 0
        q = deque([s])
        while q:
            u = q.popleft()
            for v, cap, _ in self.G[u]:
                if self.depth[v] == -1 and cap > 0:
                    self.depth[v] = self.depth[u] + 1
                    q.append(v)
        return self.depth[t] != -1

    # 从 u 出发,沿着深度优先搜索的路径,不断增广
    def dfs(self, u, t, f=inf):
        if u == t:
            return f
        max_flow = 0
        while self.cur[u] < len(self.G[u]):
            v, cap, rev = self.G[u][self.cur[u]]
            # 只有当 u 的深度加 1 之后等于 v 的深度时,我们才让他流 [不能走回头路]
            if cap > 0 and self.depth[v] == self.depth[u] + 1:
                # 流多少呢,能装多少流就流多少
                df = self.dfs(v, t, min(f - max_flow, cap))
                if df > 0:
                    self.G[u][self.cur[u]][1] -= df
                    self.G[v][rev][1] += df
                    max_flow += df
                    if max_flow == f:
                        break
                else:
                    self.depth[v] = 0
            # 只有当前面的边都已经流满/不可达,我们才更新 cur[u],下一次直接从第cur[u]条边开始枚举
            self.cur[u] += 1
        return max_flow

    # s 是源点,t 是汇点
    def max_flow(self, s, t):
        total_flow = 0
        # 当找不到增广路时,bfs 返回 false
        while self.bfs(s, t):
            self.cur = [0] * self.n
            flow = self.dfs(s, t)
            while flow > 0:
                total_flow += flow
                flow = self.dfs(s, t)
        return total_flow

    # 用手写栈优化dfs
    @bootstrap
    def dfs_bootstrap(self, u, t, f=inf):
        if u == t:
            yield f
        max_flow = 0
        while self.cur[u] < len(self.G[u]):
            v, cap, rev = self.G[u][self.cur[u]]
            # 只有当 u 的深度加 1 之后等于 v 的深度时,我们才让他流 [不能走回头路]
            if cap > 0 and self.depth[v] == self.depth[u] + 1:
                # 流多少呢,能装多少流就流多少
                df = yield self.dfs_bootstrap(v, t, min(f - max_flow, cap))
                if df > 0:
                    self.G[u][self.cur[u]][1] -= df
                    self.G[v][rev][1] += df
                    max_flow += df
                    if max_flow == f:
                        break
                else: self.depth[v] = 0
            # 只有当前面的边都已经流满/不可达,我们才更新 cur[u],下一次直接从第cur[u]条边开始枚举
            self.cur[u] += 1
        yield max_flow

    def max_flow_bootstrap(self, s, t):
        total_flow = 0
        # 当找不到增广路时,bfs 返回 false
        while self.bfs(s, t):
            self.cur = [0] * self.n
            flow = self.dfs_bootstrap(s, t)
            while flow > 0:
                total_flow += flow
                flow = self.dfs_bootstrap(s, t)
        return total_flow

def solve():
    @bootstrap
    def path(x):
        res.append(x + 1)
        vis[x] = 1
        for y, cap, rev in mf.G[x]:
            if y == s or y == t: continue
            y = y - n
            if not cap and not vis[y]: yield path(y)
        yield None

    N = I()
    n = 1570
    mf = Maxflow(n+n+2)
    s,t = n+n,n+n+1
    ans,cnt = 0, 0
    total_flow = 0
    for i in range(n):
        ans += 1
        mf.add_edge(s,i,1)
        mf.add_edge(i+n,t,1)
        for j in range(i):
            tmp = i+1+j+1
            if int(sqrt(tmp))**2 == tmp:
                mf.add_edge(j, i+n, 1)
        tmp = total_flow
        while mf.bfs(s, t):
            mf.cur = [0] * mf.n
            flow = mf.dfs_bootstrap(s, t)
            while flow > 0:
                total_flow += flow
                flow = mf.dfs_bootstrap(s, t)
        if tmp == total_flow: cnt += 1
        if cnt > N:
            ans -= 1
            break

    print1(ans)
    vis = [0]*(ans+1)
    for i in range(ans):
        if not vis[i]:
            res = [i]
            path(i)
            print_arr(res)

solve()

最小费用最大流(Minimum cost maximum flow,简称 M C M F MCMF MCMF

常见的建模思路总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FarawayTraveler_Yuch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值