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


    写这个博客主要是为了自己复习用。这个东西看起来虽然不好理解,但实际上有着非常美妙的性质。(所以比较容易忘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条流,行一条,列一条
    } 
    
    
  • 相关阅读:
    【XSY3905】字符串题(lyndon串,构造)
    【XSY3904】直线(分块)
    收藏的数学网址
    Music!
    Codeforces Global Round 12
    Codeforces Round #698 (Div. 2)
    Codeforces Round #727 (Div. 2)
    Croatian Open Competition in Informatics (COCI) 2020/2021 — Round #2
    Tokio Marine & Nichido Fire Insurance Programming Contest 2021 (AtCoder Regular Contest 122)
    NowCoder IOI 周赛 26 [提高组]
  • 原文地址:https://www.cnblogs.com/maomao9173/p/10336427.html
Copyright © 2020-2023  润新知