上下界网络流——概念解析与快速入门(待修改)

写这个博客主要是为了自己复习用。这个东西看起来虽然不好理解,但实际上有着非常美妙的性质。(所以比较容易忘QAQ)所以我就打算把它稍微做一点个人笔记啦~

如果对你有帮助的话,记得点个赞哦~

(Part) (1) :无源汇上下界可行流

概念理解:

  • 没有(S)(T)节点来统一发出和回收流量。

    • 如果我们把网络流类比成蹄形磁铁的磁感线的话,无源汇上下界可行流就相当于考虑磁铁内部从(S)极到(N)极磁感线时的情况。
    • 如果不太好理解,您还可以理解为一个循环的管道里面流淌着奇怪的液体(比如液态钾钠合金),然后一直流啊流啊流啊流着在里面循环。
  • 依然要保持流量出入平衡。(每个点进多少出多少)

  • 而且。。。你要保证他的流量在一个([l, r])的范围内

  • 这咋整啊?

我们先啥都不管,对每条边先把下界塞上再讲。这个时候,流量出入显然不平衡,但已经满足了基本的下界要求。填完下界,剩下的容量就可以为所欲为啦。接下来只要在这个基础上,再填上一些必要的流量保持平衡就可以了。

add_edge (u, v, upp[i] - low[i]); // u -> v, flow = upp[i] - low[i]

因为(low[i])被我们事先用完了,所以我们的容量就统一减少了(low[i])这一块。

前面我们已经不计后果地把下界堵着了。自己欠下的锅迟早要背的,这里我们就需要一个补偿流来维护流量的出入平衡。对于每一个点,我们计算它的所有流入流量减去流出流量,得到一个(flow[i])

flow[u] -= low[i];
flow[v] += low[i];

(flow[i]>0)说明流进来的比较多,需要一个流出的补偿流。

(flow[i] < 0)说明流出去的比较多,需要一个流入的补偿流。

仔细一想,搞补偿流还不简单?直接在前后的边上加流量就好啦。但是事情并没有那么简单——首先这么操作很麻烦,其次(dinic)算法是针对有源网络流进行的,循环流它是无能为力的。

然后就有了一个非常伟大的想法——里应不行,我外合不久是了?

所以我们新建一个(SS)点和一个(TT)点,接下来的操作就变成了这样:

  • (flow[i]>0)说明流进来的比较多,需要一个流出的补偿流。为了引出这个流出的补偿流,我们添加一个流量为(+flow[i])的,由SS流入到当前点的流。

  • (flow[i] < 0)说明流出去的比较多,需要一个流入的补偿流。为了引出这个流入的补偿流,我们添加一个流量为(-flow[i])的,由当前点流出到TT的流。

int s = 0, t = n + 1;
for (int i = 1; i <= n; ++i) {
    if (flow[i] < 0) { 
        //流出多 -> 需要流入的补偿流 -> 通过增加外源流出来引出
        add_edge (i, t, -flow[i]);
        add_edge (t, i, 0);		 
    } else if (flow[i] > 0){
        //流入多 -> 需要流出的补偿流 -> 通过增加外源流入来引出
        max_flow += flow[i];
        add_edge (s, i, +flow[i]);
        add_edge (i, s, 0); 
    }
}

通过添加相反方向的边,对之前的不平衡作出抵消,我们就实现了出入平衡啦~(也可以像上面那样理解,同样很清楚。)

这样把边一连,套上(dinic),我们就跑出来了一个可行流(是当前图的最大流,但不一定真的是所有可以造出来的流中的最大!)根据出入平衡,这里一定有(∑flow[i] (flow[i] > 0) = max\_flow) 。通过这个等式,我们就可以检验可行流的存在性啦~

if (max_flow != can_flow) {
	puts ("please go home to sleep");
	return 0;
}

好啦,睡前的最后一个问题:既然我们把本来应该在里面穿插着的补偿流引到了外面,我们又怎么求出所有边使用的可行流大小呢?

像前面所说的:我们添加引向(S)(T)的边,是为了引出补偿流。所以说,虽然补偿流来的时候不好算,但是走的时候是会留下痕迹哒。(想一下反向边!)所以我们只需要在当前边流量下界的基础上,添加一个当前边的反向边流量(就是之前溜走的补偿流流量啦~),就得到最终的可行流啦~

for (int i = 1; i <= m; ++i) {
	printf ("%d
", e[((i - 1) * 2) ^ 1].f + low[i]);
}

剩下还有三个延伸拓展的先咕着,回头再说啦(QwQ)


更新啦更新啦(QwQ)

(Part 2 :)有源汇上下界可行流

  • 相对比较简单。

  • 只需要添加一个从(t)(s)的流量为(INF)的边就可以变成无源汇的啦~

  • 注意需要设一个额外的超级源(和原图所说不同的源)(ss)(tt)

add_edge (t, s, INF);
add_edge (s, t, 0);	

$Part 3 : $有源汇上下界最大流

  • (Part2)的基础上来做嘛(QwQ)

  • 首先通过(Part2)的建模得到了一个上下界可行流

  • 我们记录一下这个可行流的流量

    • 可行流所有流量必然从(s)(t),所以我们只需要记录从(t)(s)的流量就是当前可行流已经流走的流量
    • 注意不能直接用已经求出来的(max\_flow)!那个是建模后的流!不是最初的(s->t)可行流!
  • 然后呢?

    • 断掉(s)(t)的双向连边
    • 跑出来从(s)(t)的剩下流量,累加即可。
#include <bits/stdc++.h>
using namespace std;

const int N = 210;
const int M = 30010;
const int INF = 0x3f3f3f3f;

int n, m, u, v, low[M], upp[M];
int s, t, cnt = -1, head[N], flow[N];

queue <int> q;
#define fpop(q) q.front();q.pop()

int cur[N], deep[N];

struct edge {
	int nxt, to, f;
}e[M];

void add_edge (int from, int to, int flw) {
	e[++cnt].nxt = head[from];
	e[cnt].to = to;
	e[cnt].f = flw;
	head[from] = cnt;
}

bool bfs (int s, int t) {
	memcpy (cur, head, sizeof (head));
	memset (deep, 0x3f, sizeof (deep));
	deep[s] = 0; q.push (s);
	while (!q.empty ()) {
		int u = fpop (q);
		for (int i = head[u]; ~i; i = e[i].nxt) {
			int v = e[i].to;
			if (deep[v] == INF && e[i].f) {
				deep[v] = deep[u] + 1;
				q.push (v);
			}
		}
	}
	return deep[t] != INF;
}

int dfs (int u, int t, int lim) {
	if (u == t || !lim) {
		return lim;
	}
	int tmp, flow = 0;
	for (int &i = cur[u]; ~i; i = e[i].nxt) {
		int v = e[i].to;
	    if (deep[v] == deep[u] + 1) {
			tmp = dfs (v, t, min (lim, e[i].f));
			lim -= tmp;
			flow += tmp;
			e[i ^ 0].f -= tmp;
			e[i ^ 1].f += tmp;
			if (!lim) break;
		}


	}
	return flow;
}


int main () {
	memset (head, -1, sizeof (head));
	cin >> n >> m >> s >> t;
	add_edge (t, s, INF);
	add_edge (s, t, 0);
	for (int i = 1; i <= m; ++i) {
		cin >> u >> v >> low[i] >> upp[i];
		add_edge (u, v, upp[i] - low[i]);
		add_edge (v, u, 0);
		flow[v] += low[i];
		flow[u] -= low[i];
	}
	int ss = n + 1, tt = n + 2, can_flow = 0, max_flow = 0;
	for (int i = 1; i <= n; ++i) {
		if (flow[i] > 0) {
			can_flow += flow[i];
			add_edge (ss, i, +flow[i]);
			add_edge (i, ss, 0);
		} else {
			add_edge (i, tt, -flow[i]);
			add_edge (tt, i, 0);
		}
	}
    while (bfs (ss, tt)) {
		max_flow += dfs (ss, tt, INF);
	}
	if (max_flow != can_flow) {
		puts ("please go home to sleep");
		return 0;
	}
	int ans = e[1].f;
	e[0].f = e[1].f = 0;
	while (bfs (s, t)) {
		ans += dfs (s, t, INF);
	}
	printf ("%d
", ans);
}

(Part 4:)有源汇上下界最小流

  • 类似于(Part3)的考虑方法。

  • 唯一的区别就是,在最后一步上我们不跑(s -> t)的剩余流量进行累加,而是跑(t->s)的流量把它减去(也就是把已经跑过去的流量退流的过程。)因为有最初的无源汇建模,所以一定可以满足下界的条件要求啦(QwQ)

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 50010;
const int M = 400010;
const int INF = 0x3f3f3f3f3f3f3f3f;

int n, m, s, t, u, v, cnt = -1, low[M], upp[M], flow[N], head[N];

struct edge {
	int nxt, to, f;
}e[M];

void add_edge (int from, int to, int flw) {
	e[++cnt].nxt = head[from];
	e[cnt].to = to;
	e[cnt].f = flw;
	head[from] = cnt;
}

int cur[N], deep[N];
queue <int> q;
#define fpop(q) q.front (); q.pop ();

bool bfs (int s, int t) {
	memcpy (cur, head, sizeof (head));
	memset (deep, 0x3f, sizeof (deep));
	deep[s] = 1; q.push (s);
	while (!q.empty ()) {
		int u = fpop (q);
		for (int i = head[u]; ~i; i = e[i].nxt) {
			int v = e[i].to;
			if (deep[v] == INF && e[i].f) {
				deep[v] = deep[u] + 1;
				q.push (v);
			}
		}
	}
	return deep[t] != INF;
}

int dfs (int u, int t, int lim) {
	if (u == t || !lim) {
		return lim;
	}
	int tmp, flow = 0;
	for (int &i = cur[u]; ~i; i = e[i].nxt) {
		int v = e[i].to;
		if (deep[v] == deep[u] + 1) {
			tmp = dfs (v, t, min (lim, e[i].f));
			lim -= tmp;
			flow += tmp;
			e[i ^ 0].f -= tmp;
			e[i ^ 1].f += tmp;
			if (!lim) break;
		}
	}
	return flow;
}

signed main () {
	memset (head, -1, sizeof (head));
	cin >> n >> m >> s >> t;
	add_edge (t, s, INF);
	add_edge (s, t, 0);
	for (int i = 1; i <= m; ++i) {
		cin >> u >> v >> low[i] >> upp[i];
		add_edge (u, v, upp[i] - low[i]);
		add_edge (v, u, 0);
		flow[u] -= low[i];
		flow[v] += low[i];
	}
	int ss = n + 1, tt = n + 2;
	int max_flow = 0, can_flow = 0;
	for (int i = 1; i <= n; ++i) {
		if (flow[i] > 0) {
			can_flow += flow[i];
			add_edge (ss, i, +flow[i]);
			add_edge (i, ss, 0);
		} else {
			add_edge (i, tt, -flow[i]);
			add_edge (tt, i, 0);
		}
	}
	while (bfs (ss, tt)) {
		max_flow += dfs (ss, tt, INF);
	}
	if (can_flow != max_flow) {
		puts ("please go home to sleep");
		return 0;
	}
	int ans = e[1].f;
	e[0].f = e[1].f = 0;
	while (bfs (t, s)) {
		ans -= dfs (t, s, INF);
	}
	cout << ans << endl;
}

最后提醒:务必不要把(N)(M)写反啊(QAQ)(而且还不能被样例检查出来!被坑好几次了哇(QAQ)


例题:

(Luogu)(P4843)清理雪道

  • 水题,直接做一次拓扑连(s)(t)就是裸的上下界网络流,注意下界为(1)

  • (Code)

#include <bits/stdc++.h>
using namespace std;

#define LL long long

const int N = 110;
const int M = 20010;
const int INF = 0x3f3f3f3f;
const LL INFF = 0x3f3f3f3f3f3f3f3f;

struct edge {
    int nxt, to; LL f;
}e[M];

int cnt = -1, head[N];

void add_edge (int from, int to, LL flw) {
    e[++cnt].nxt = head[from];
    e[cnt].to = to;
    e[cnt].f = flw;
    head[from] = cnt;
}

int n, m, v, inn[N], out[N], flow[N];

int cur[N], deep[N];

queue <int> q;
#define fpop(x) x.front();x.pop()

bool bfs (int s, int t) {
    memcpy (cur, head, sizeof (head));
    memset (deep, 0x3f, sizeof (deep));
    deep[s] = 1; q.push (s);
    while (!q.empty ()) {
        int u = fpop (q);
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].to;
            if (deep[v] == INF && e[i].f) {
                deep[v] = deep[u] + 1;
                q.push (v);
            }
        }
    }
    return deep[t] != INF;
}

LL dfs (int u, int t, LL lim) {
    if (u == t || !lim) {
        return lim;
    }
    LL tmp = 0, flow = 0;
    for (int &i = cur[u]; ~i; i = e[i].nxt) {
        int v = e[i].to;
        if (deep[v] == deep[u] + 1) {
            tmp = dfs (v, t, min (lim, e[i].f));
            lim -= tmp;
            flow += tmp;
            e[i ^ 0].f -= tmp;
            e[i ^ 1].f += tmp;
            if (!lim) break;
        }
    }
    return flow;
}

int main () {
    memset (head, -1, sizeof (head));
    cin >> n;
    int s = n + 1, t = n + 2;
    int ss = n + 3, tt = n + 4;
    add_edge (t, s, INFF);
    add_edge (s, t, 0);
    //t -> s [0, INFF];
    for (int u = 1; u <= n; ++u) {
        cin >> m;
        for (int i = 1; i <= m; ++i) {
            cin >> v;
            ++inn[v];
            ++out[u];
            add_edge (u, v, INF - 1);
            add_edge (v, u, 0);
            //u -> v [1, INF];
            flow[v] += 1;
            flow[u] -= 1;
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (inn[i] == 0) {
            add_edge (s, i, INFF);
            add_edge (i, s, 0);
        }
        if (out[i] == 0) {
            add_edge (i, t, INFF);
            add_edge (t, i, 0);
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (flow[i] > 0) {
            add_edge (ss, i, +flow[i]);
            add_edge (i, ss, 0);
        } else {
            add_edge (i, tt, -flow[i]);
            add_edge (tt, i, 0);
        }
    }
    while (bfs (ss, tt)) {
        dfs (ss, tt, INFF);
    }
    LL ans = e[1].f;
    e[0].f = e[1].f = 0;
    while (bfs (t, s)) {
        ans -= dfs (t, s, INFF); 
    }
    cout << ans << endl;
}

(Luogu)(P4043)】支线剧情

  • 比较特殊,因为是上下界费用 可行流 的模型。

  • 处理方式按照上下界网络可行流的方式,套上费用流的板子即可。(而不是最小流(QwQ)所以并不需要退流!)

  • 坑点:每个支线剧情都可以随时结束,所以每个点都要向(T)连边。

  • 一样的点数和边树,不同的图效率区别可能 非常大 !!!

  • (Code)

#include <bits/stdc++.h>
using namespace std;

#define LL long long
const int N = 310;
const int M = 100800;
const int INF = 0x3f3f3f3f;
const LL INFF = 0x3f3f3f3f3f3f3f3f;

int n, k, cnt = -1, head[N], flow[N];

struct edge {
    int nxt, to, w; LL f;
}e[M];

void add_edge (int from, int to, LL flw, int val) {
    e[++cnt].nxt = head[from];
    e[cnt].to = to;
    e[cnt].w = val;
    e[cnt].f = flw;
    head[from] = cnt;
}

int vis[N], dis[N]; LL fflow[N];
int pre_node[N], pre_edge[N];
queue <int> q;
#define fpop(x) x.front();x.pop()

bool spfa (int s, int t) {
    memset (vis, 0, sizeof (vis));
    memset (dis, 0x3f, sizeof (dis));
    memset (fflow, 0x3f, sizeof (fflow));
    dis[s] = 0; vis[s] = true; q.push (s);
    while (!q.empty ()) {
        int u = fpop (q);
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].to;
            // printf ("%d -> %d dis[u] = %d flow[u] = %lld
", u, v, dis[u], fflow[u]);
            if (dis[v] > dis[u] + e[i].w && e[i].f) {
                dis[v] = dis[u] + e[i].w;
                fflow[v] = min (fflow[u], e[i].f);
                pre_edge[v] = i;
                pre_node[v] = u;
                if (!vis[v]) {
                    vis[v] = true;
                    q.push (v);
                }
            }
 		}
        vis[u] = false;
    }
    return fflow[t] != INFF;
}

int main () {
    memset (head, -1, sizeof (head));
    cin >> n;
    int v, w;
    int s = n + 1, t = n + 2;
    int ss = n + 3, tt = n + 4;
    add_edge (t, s, INFF, 0);
    add_edge (s, t, 0, 0);
    add_edge (s, 1, INFF, 0);
    add_edge (1, s, 0, 0);
    LL min_cost = 0;
    for (int u = 1; u <= n; ++u) {
        cin >> k;
        for (int i = 1; i <= k; ++i) {
            cin >> v >> w;
            add_edge (u, v, INF, +w);
            add_edge (v, u, 0, -w);
            //u -> v : flow [1, INF + 1], cost = w;
            flow[u] -= 1;
            flow[v] += 1;
            min_cost += w;
        }
        add_edge (u, t, INFF, 0);
        add_edge (t, u, 0, 0);
    }
    for (int i = 1; i <= n; ++i) {
        if (flow[i] > 0) {
            add_edge (ss, i, +flow[i], 0);
            add_edge (i, ss, 0, 0);
        } else {
            add_edge (i, tt, -flow[i], 0);
            add_edge (tt, i, 0, 0);
        }
    }
    while (spfa (ss, tt)) {
        // printf ("dis[tt] = %d, fflow[tt] = %d
", dis[tt], fflow[tt]);
        min_cost += (LL)dis[tt] * (LL)fflow[tt];
        int u = tt;
        while (u != ss) {
            e[pre_edge[u] ^ 0].f -= fflow[tt];
            e[pre_edge[u] ^ 1].f += fflow[tt];
            u = pre_node[u];
        }
    }
    cout << min_cost << endl;
}

(Luogu)(P4553)】80人环游世界

  • 又一个上下界费用流!(吐血(.jpg)

  • 其实真的不是很难,但是不知道为啥最开始就是出锅了,重构一遍就没事了。

  • 还是介绍一下建图思路吧,毕竟写了很久。

    • 把每个地点拆成两个点(A(i))(B(i))作为入点和出点。通过控制二者流量上下界为([v_i, v_i])来使每个地点被且仅被(v_i)个人访问。
    • (p -> A(i)) (f = INF) (cost = 0) 任一点均可开始航程
    • (B(i) -> t) (f = INF) (cost = 0) 任一点均可结束航程
    • (s -> p) (f = m) (cost = 0) 控制总共(m)个人访问
    • (t -> s) (f = INF) (cost = 0) 把有源汇流转无源汇流,随后再添加超级源汇进行处理。
    • (B (u) -> A(v)) (f = INF) (cost = val) 处理地点之间的到达关系
    • (ss -> [flow_i > 0]) (f = INF) (w = 0) 上下界网络流套路建边,处理补偿流。
    • ([flow_i<0] -> tt) (f = INF) (w = 0) 同上。
#include <bits/stdc++.h>
using namespace std;

const int N = 1000;
const int M = 200010;
const int INF = 0x3f3f3f3f;

int cnt = -1, head[N];

struct edge {
    int nxt, to, f, w;
}e[M];

void add_edge (int from, int to, int flw, int val) {
    e[++cnt].nxt = head[from];
    e[cnt].to = to;
    e[cnt].f = flw;
    e[cnt].w = val;
    head[from] = cnt;
}

void add_len (int u, int v, int f, int w) {
    add_edge (u, v, f, +w);
    add_edge (v, u, 0, -w);
}

int n, m, u, v, ff[N], cost;

int A (int x) {return n * 0 + x;}
int B (int x) {return n * 1 + x;}

int vis[N], dis[N], flow[N];
int pre_node[N], pre_edge[N];
queue <int> q;
#define fpop(x) x.front (); x.pop ()

bool spfa (int s, int t) {
    memset (vis, 0, sizeof (vis));
    memset (dis, 0x3f, sizeof (dis));
    memset (flow, 0x3f, sizeof (flow));
    vis[s] = true; dis[s] = 0; q.push (s);
    while (!q.empty ()) {
        int u = fpop (q);
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].to;
            if (dis[v] > dis[u] + e[i].w && e[i].f) {
                dis[v] = dis[u] + e[i].w;
                flow[v] = min (flow[u], e[i].f);
                pre_edge[v] = i;
                pre_node[v] = u;
                if (!vis[v]) {
                    vis[v] = true;
                    q.push (v);
                }
            }
        }
        vis[u] = false;
    }
    return dis[t] != INF;
}

int main () {
    memset (head, -1, sizeof (head));
    cin >> n >> m;
    int s = n * 2 + 1, t = n * 2 + 2;
    int p = n * 2 + 3, ss = n * 2 + 4, tt = n * 2 + 5;
    for (int i = 1; i <= n; ++i) {
        cin >> v;
        ff[A (i)] -= v;
        ff[B (i)] += v;
        //A (i) -> B (i)  flow = [v, v];
    }
    for (int i = 1; i <= n; ++i) {
        add_len (B (i), t, INF, 0); //任一点结束航程
        add_len (p, A (i), INF, 0); //任一点开始航程
    }
    add_len (s, p, m, 0); //m人次
    add_len (t, s, INF, 0); //转无源汇
    for (int u = 1; u <= n - 1; ++u) {
        for (int v = u + 1; v <= n; ++v) {
            cin >> cost;
            if (cost == -1) continue;
            add_len (B (u), A (v), INF, cost);
        }
    }
    for (int i = 1; i <= B (n); ++i) {
        if (ff[i] > 0) {
            add_len (ss, i, +ff[i], 0);
        } else {
            add_len (i, tt, -ff[i], 0);
        }
    }
    int min_cost = 0;
    while (spfa (ss, tt)) {
        min_cost += dis[tt] * flow[tt];
        int u = tt;
        while (u != ss) {
            e[pre_edge[u] ^ 0].f -= flow[tt];
            e[pre_edge[u] ^ 1].f += flow[tt];
            u = pre_node[u];
        }
    }
    cout << min_cost << endl;
}

(Luogu)(P4311)】士兵占领​

  • 超级裸的上下界网络流

  • 建图胡跑,思路理清即可。

#include <bits/stdc++.h>
using namespace std;

const int N = 20010;
const int M = 400010;
const int INF = 0x3f3f3f3f;

int cnt = -1, head[N];
int n, m, k, r[N], c[N], ban[N][N];

int nd (int x, int y) {return (x - 1) * m + y;}
int rr (int x) {return n * m + x;}
int cc (int x) {return n * m + n + x;}

struct edge {
    int nxt, to, f;
}e[M];

void add_edge (int from, int to, int flw) {
    e[++cnt].nxt = head[from];
    e[cnt].to = to;
    e[cnt].f = flw;
    head[from] = cnt;
}

void add_len (int u, int v, int f) {
    add_edge (u, v, f);
    add_edge (v, u, 0);
}

queue <int> q;
#define fpop(x) x.front();x.pop()
int cur[N], deep[N], flow[N];

bool bfs (int s, int t) {
    memcpy (cur, head, sizeof (head));
    memset (deep, 0x3f, sizeof (deep));
    deep[s] = 1; q.push (s);
    while (!q.empty ()) {
        int u = fpop (q);
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int v = e[i].to;
            if (deep[v] == INF && e[i].f) {
                deep[v] = deep[u] + 1;
                q.push (v);
            }
        }
    }
    return deep[t] != INF;
}

int dfs (int u, int t, int lim) {
    if (u == t || !lim) {
        return lim;
    }
    int tmp = 0, flow = 0;
    for (int &i = cur[u]; ~i; i = e[i].nxt) {
        int v = e[i].to;
        if (deep[v] == deep[u] + 1) {
            tmp = dfs (v, t, min (lim, e[i].f));
            lim -= tmp;
            flow += tmp;
            e[i ^ 0].f -= tmp;
            e[i ^ 1].f += tmp;
            if (!lim) break;
        }
    }
    return flow;
}

int main () {
    memset (head, -1, sizeof (head));
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i) cin >> r[i];
    for (int i = 1; i <= m; ++i) cin >> c[i];
    int x, y;
    int s = n * m + n + m + 1;
    int t = n * m + n + m + 2;
    int ss = n * m + n + m + 3;
    int tt = n * m + n + m + 4;
    for (int i = 1; i <= k; ++i) {
        cin >> x >> y;
        ban[x][y] = true;
    }
    add_len (t, s, INF);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (!ban[i][j]) {
                add_len (s, nd (i, j), 2);
                add_len (nd (i, j), rr (i), 1);
                add_len (nd (i, j), cc (j), 1);
            }
        }
    }
    for (int i = 1; i <= n; ++i) {
        add_len (rr (i), t, INF - r[i]);
        flow[rr (i)] -= r[i];
        flow[t] += r[i];
    }
    for (int i = 1; i <= m; ++i) {
        add_len (cc (i), t, INF - c[i]);
        flow[cc (i)] -= c[i];
        flow[t] += c[i];
    }
    int can_flow = 0;
    for (int i = 1; i <= t; ++i) {
        if (flow[i] > 0) {
            can_flow += flow[i];
            add_len (ss, i, +flow[i]);
        }
        if (flow[i] < 0) {
            add_len (i, tt, -flow[i]);
        }
    }
    int max_flow = 0;
    while (bfs (ss, tt)) {
        max_flow += dfs (ss, tt, INF);
    }
    // cout << "max_flow = " << max_flow << " can_flow = " << can_flow << endl;
    if (max_flow != can_flow) {
        puts ("JIONG!");
        return 0;
    }
    int ans = e[1].f; e[0].f = e[1].f = 0;
    while (bfs (t, s)) {
        ans -= dfs (t, s, INF);
        // cout << "ans = " << ans << endl;
    }
    cout << ans / 2 << endl;
    //k个士兵对应2k条流,行一条,列一条
} 

原文地址:https://www.cnblogs.com/maomao9173/p/10336427.html