写这个博客主要是为了自己复习用。这个东西看起来虽然不好理解,但实际上有着非常美妙的性质。(所以比较容易忘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\n", 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\n", 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\))
例题:
-
水题,直接做一次拓扑连\(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;
}
-
比较特殊,因为是上下界费用 可行流 的模型。
-
处理方式按照上下界网络可行流的方式,套上费用流的板子即可。(而不是最小流\(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\n", 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\n", 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;
}
-
又一个上下界费用流!(吐血\(.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;
}
#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条流,行一条,列一条
}