• 网络流24题の详解


    \(1.\)洛谷P2756 飞行员配对方案问题
    题意:派出最多架的飞机,并且每架飞机上都是一个英国飞行员和外籍飞行员
    分析:经典的二分图匹配问题,将英国飞行员当做二分图的左部,外籍飞行员当做二分图的右部。可以用匈牙利算法求解,但这里使用网络流(最大流)。
    考虑如何建图:
    设立一个源点\(st\),一个汇点\(ed\)
    \(I.\)让源点\(st\)向二分图的左部建一条流量为\(1\)的边,
    \(II.\)让二分图的右部向汇点\(ed\)建一条流量为\(1\)的边,
    \(III.\)如果英国飞行员\(u\)可以和外籍飞行员\(v\)配合,那么\(u\)\(v\)建一条流量为\(1\)的边。
    最后跑一遍最大流即可得到最大匹配数。
    如何输出配对方案:
    遍历所有的英国飞行员所连的边,如果此边\((u,v)\)流量不是\(1\)而是\(0\)说明\((u,v)\)配对。注意源点向英国飞行员所连的边流量会为0,但此时不应输出。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 300 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, u, v, st, ed, cnt, ans, head[maxn];
    int flag, maxflow, vis[maxn], cur[maxn], deep[maxn];
    
    struct Graph {
    	int v, w, next;
    } edge[2 * maxn * maxn];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= n + m + 5; i++) vis[i] = 0, cur[i] = head[i], deep[i] = inf;
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (deep[v] > deep[u] + 1 && edge[i].w) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxflow += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		cur[u] = i;
    		if (deep[v] == deep[u] + 1 && edge[i].w) {
    			if (now = dfs(v, ed, min(flow - used, edge[i].w))) {
    				used += now;
    				edge[i].w -= now;
    				edge[i ^ 1].w += now;
    				if (used == flow) break;
    			}
    		}
    	}
    	return used;
    }
    
    int dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) flag = 0, dfs(st, ed, inf);
    	}
    	return maxflow;
    }
    
    int main() {
    	memset(head, -1, sizeof head);
    	scanf("%d %d", &n, &m);
    	st = 0, ed = m + 1;
    	while (~scanf("%d %d", &u, &v)) {
    		if (u == -1 && v == -1) break;
    		addedge(u, v, 1), addedge(v, u, 0);
    	}
    	for (int i = 1; i <= n; i++) addedge(st, i, 1), addedge(i, st, 0);
    	for (int i = n + 1; i <= m; i++) addedge(i, ed, 1), addedge(ed, i, 0);
    	ans = dinic(st, ed);
    	if (!ans) return 0 * puts("No Solution!");
    	printf("%d\n", ans);
    	for (int i = 1; i <= n; i++) {
    		for (int j = head[i]; ~j; j = edge[j].next) {
    			if (edge[j].v != st && edge[j].w == 0 && edge[j ^ 1].w == 1)
    				printf("%d %d\n", i, edge[j].v);
    		}
    	}
    	return 0;
    }
    

    \(2.\)洛谷P2764 最小路径覆盖问题
    做这道题首先我们要知道一个关于二分图的公式,

    最小路径覆盖数 = 图的顶点数 - 图相应的二分图的最大匹配数

    图转化为相应的二分图:对每个点拆点,拆成入点\(x_1\)(\(i\))和出点\(x_2\)(\(i+n\)),将这些入点作为二分图的左部,出点作为二分图的右部。
    \(1\)中一样建立源点与汇点,源点与左部相连,右部与汇点相连。
    对于边(\(u,v\)),我们将\(u\)连向\(v+n\)即可。
    跑一遍最大流,得到最大匹配数,则最小路径覆盖数就得到了。
    如何输出路径:遍历所有的边(\(u,v\))(正向边即可),如果这条边的流量为\(0\),说明这条边一定在某条路径上。我们用非路径压缩的并查集来维护这些关系,如果此边流量为\(0\),则将\(v\)并向\(u\)。那么最后一定有祖先为自己的点,\(dfs\)这些点即可。判断边的时候要注意源点的影响,

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 300 + 5;
    const int maxm  = 12000 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, cnt, pre[maxn], head[maxn];
    int u, v, st, ed, flag, maxflow, deep[maxn], vis[maxn], cur[maxn];
    
    struct Graph {
    	int u, v, w, next;
    } edge[maxm << 1];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].u = u;
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= n + n + 5; i++) deep[i] = inf, vis[i] = 0, cur[i] = head[i];
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v, w = edge[i].w;
    			if (w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxflow += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		cur[u] = i;
    		int v = edge[i].v;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    void dfs(int x) {
    	printf("%d ", x);
    	vis[x] = 1;
    	for (int i = head[x]; ~i; i = edge[i].next) {
    		if (!vis[edge[i].v] && edge[i].w == 0 && edge[i].v > n) {
    			dfs(edge[i].v - n);
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= m; i++) {
    		scanf("%d %d", &u, &v);
    		addedge(u, v + n, 1), addedge(v + n, u, 0);
    	}
    	st = 0, ed = n + n + 1;
    	for (int i = 1; i <= n; i++) addedge(st, i, 1), addedge(i, st, 0);
    	for (int i = n + 1; i <= n + n; i++) addedge(i, ed, 1), addedge(ed, i, 0);
    	dinic(st, ed);
    	for (int i = 1; i <= n; i++) pre[i] = i;
    	for (int j = 0; j <= cnt; j++) {
    		if (edge[j].u >= 1 && edge[j].u <= n && edge[j].v > n && edge[j].v <= n + n && edge[j].w == 0) {
    		//	if (pre[edge[j].v - n] != pre[edge[j].u]) 
    			pre[edge[j].v - n] = pre[edge[j].u];
    		}
    	}
    	memset(vis, 0, sizeof vis);
    	for (int i = 1; i <= n; i++) {
    		if (pre[i] == i) {
    			dfs(i);
    			puts("");
    		}
    	}
    	printf("%d\n", n - maxflow);
    	return 0;
    }
    

    \(3.\)洛谷P2765 魔术球问题
    分析:因为柱子数是固定不变的,我们可以将\(n\)根柱子理解为\(n\)条路径。那么问题就可以转化为不断向路径中加入数,直到加入某个数使得路径数\(>\)\(n\)。从\(1\)开始枚举直到不满足条件。
    如何建图:
    每次新枚举一个数\(x\),那么它有可能是一个柱子的开头,所以我们从源点向\(x\)建一条流量为\(1\)的边,它也有可能是某个柱子的最后一个数,所以\(x\)向汇点连一条流量为\(1\)的边。然后现在就是考虑数和数之间的边,题目限制\(x\)只有和相邻数相加为完全平方数时才能放入,所以枚举\(y\)\(\in\)\([1,x-1]\),如果\(y\)能与\(x\)形成完全平方数就和\(x\)建边(\(x\)作为右部)。
    然后在残余网络上跑最大流得到每次的最大匹配数,每次让总顶点数减去其再判断即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 7000 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, cnt, head[maxn];
    int st, ed, ans, now, maxf, flag, deep[maxn], vis[maxn], cur[maxn], path[maxn];
    
    struct Graph {
    	int v, w, next;
    } edge[200000];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= ed; i++) deep[i] = inf, vis[i] = 0, cur[i] = head[i];
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxf += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		cur[u] = i;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	memset(head, -1, sizeof head);
    	st = 0, ed = 5001;
    	while (true) {
    		ans++, now++;
    		addedge(st, now, 1), addedge(now, st, 0);
    		for (int i = 1; i <= now - 1; i++) {
    			if (sqrt(i + now) == (int)(sqrt(i + now)))
    				addedge(i, now + 2000, 1), addedge(now + 2000, i, 0);
    		}
    		addedge(now + 2000, ed, 1), addedge(ed, now + 2000, 0);
    		maxf = 0, dinic(st, ed);
    		ans -= maxf;
    		if (ans > n) break;
    	}
    	printf("%d\n", now - 1);
    	for (int i = 1; i <= now - 1; i++) {
    		for (int j = head[i]; ~j; j = edge[j].next) {
    			if (!edge[j].w) {
    				path[i] = edge[j].v - 2000;
    				break;
    			}
    		}
    	}
    	memset(vis, 0, sizeof vis);
    	for (int i = 1; i <= now - 1; i++) {
    		if (vis[i]) continue;
    		int temp = i;
    		while (temp != -2000) {
    			vis[temp] = 1;
    			printf("%d ", temp);
    			temp = path[temp];
    		}
    		puts("");
    	}
    	return 0;
    }
    

    \(4.\)洛谷P2763 试题库问题
    分析:
    二分匹配,只不过左部可以匹配多个右部。
    让题的类型\(x\in[1,n]\)作为二分图的左部,让题\(y\in[n+1,n+1+m]\)作为二分图的右部。
    源点向题的类型建流量为题类型所需题数的边,题向汇点建流量为\(1\)的边(题只能用在某个类型一次),题的类型与题建流量为\(1\)的边。跑一遍最大流即可。
    输出方案:枚举题的类型所连的边,判断流量是否为\(0\)即是否匹配上即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 20000 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int k, n, m, x, y;
    int st, ed, cnt, head[maxn];
    int maxf, flag, deep[maxn], cur[maxn], vis[maxn];
    
    struct Graph {
    	int v, w, next;
    } edge[maxn << 1];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= ed; i++) vis[i] = 0, deep[i] = inf, cur[i] = head[i];
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxf += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		cur[u] = head[i];
    		int v = edge[i].v;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	memset(head, -1, sizeof head);
    	scanf("%d %d", &k, &n);
    	st = 0, ed = 20002;
    	for (int i = 1; i <= k; i++) {
    		scanf("%d", &x);
    		addedge(st, i, x), addedge(i, st, 0);
    		m += x;
    	}
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &x);
    		for (int j = 1; j <= x; j++) {
    			scanf("%d", &y);
    			addedge(y, i + k, 1), addedge(i + k, y, 0);
    		}
    		addedge(i + k, ed, 1), addedge(ed, i + k, 0);
    	}
    	dinic(st, ed);
    	for (int i = 1; i <= k; i++) {
    		printf("%d: ", i);
    		for (int j = head[i]; ~j; j = edge[j].next) {
    			if (edge[j].w == 0 && edge[j].v != st) printf("%d ", edge[j].v - k);
    		}
    		puts("");
    	}
    	return 0;
    }
    
    

    \(5.\)洛谷P3254 圆桌问题
    \(4\)是同样的问题,二分图匹配,左部可以匹配多个右部,此题不细讲。
    输出方案同\(4\)

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 500 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, x, now, cnt, sum, head[500];
    int st, ed, maxf, flag, cur[maxn], deep[maxn], vis[maxn];
    
    struct Graph {
    	int v, w, next;
    } edge[maxn * maxn];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= ed; i++) vis[i] = 0, deep[i] = inf, cur[i] = head[i];
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					q.push(v);
    					vis[v] = 1;
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    } 
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxf += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		cur[u] = i;
    		int v = edge[i].v;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	st = 0, ed = n + m + 2;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &x), sum += x;
    		addedge(st, i, x), addedge(i, st, 0);
    		for (int j = 1; j <= m; j++) {
    			addedge(i, j + n, 1), addedge(j + n, i, 0);
    		}
    	}
    	for (int i = 1; i <= m; i++) {
    		scanf("%d", &x);
    		addedge(i + n, ed, x), addedge(ed, i + n, 0);
    	}
    	dinic(st, ed);
    	if (maxf == sum) puts("1");
    	else return 0 * puts("0");
    	for (int i = 1; i <= n; i++) {
    		for (int j = head[i]; ~j; j = edge[j].next) {
    			if (edge[j].w == 0 && edge[j].v != st) {
    				printf("%d ", edge[j].v - n);
    			}
    		}
    		puts("");
    	}
    	return 0;
    }
    
    

    \(6.\)洛谷P2766 最长不下降子序列问题
    分析:
    给定正整数序列\(x_1,...,x_n\)
    \(1)\):计算其最长不下降子序列的长度\(s\)
    对于第一个询问,这是经典的\(LIS\)问题,我们使用\(dp\)即可求解,在这里应使用\(o(n^2)\)的算法来求解而不用\(o(nlogn)\)的算法,因为这样方便接下来两个询问的建图。
    这里\(dp[i]\)表示以\(x[i]\)结尾的最长不下降子序列长度。
    \(2)\):计算从给定的序列中最多可取出多少个长度为s的不下降子序列。
    显然,因为是取出即意味着每个数只能用一次。所以我们将每个点(数)拆为两个点,分别为入点和出点,然后入点与出点之间的流量为\(1\),这样我们就满足了一个点只能取一次的条件。
    考虑什么样的子序列能满足条件:这个子序列必然是以\(dp[i]==1\)\(x[i]\)作为开头,且以\(dp[i]==s\)\(x[i]\)作为结尾。(假设我取\(dp[i]==2\)\(x[i]\)作为开头,那么这个子序列最长也就是\(s-1\)的长度。)
    所以建立源点和汇点,源点向\(dp[i]==1\)\(x[i]\)建流量为\(1\)的边,\(dp[i]==s\)\(x[i]\)向汇点建流量为\(1\)的边。然后让所有\(dp[i]+1==dp[j]\)\((i<j)\)\(x[i]\)\(x[j]\)建流量为1的边。
    最后跑一遍最大流即可解决\(2)\)
    \(3)\):如果允许在取出的序列中多次使用\(x_1\)\(x_n\),则从给定序列中最多可取出多少个长度为s的不下降子序列。
    这句话翻译一下就是\(x_1\)\(x_n\)可以无限取。
    我们知道\(dp[1]\)是一定等于\(1\)的,也就是说\(x_1\)必定可以作为子序列的开头,所以我们修改\(x_1\)入点和出点之间的流量为\(inf\)。对于\(dp[n]\)来说,只有\(dp[n]==s\)时,我们才用修改它的流量,因为如果\(dp[n]=\not s\),那么无论如何只要取了\(x_n\)就一定不会形成长度为\(s\)的子序列。
    跑最大流即可

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 500 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, ans, a[maxn], dp[maxn];
    int st, ed, cnt, head[maxn << 3];
    int maxf, flag, deep[maxn << 3], cur[maxn << 3], vis[maxn << 3];
    
    struct Graph {
    	int v, w, next;
    } edge[maxn << 3];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= ed; i++) vis[i] = 0, deep[i] = inf, cur[i] = head[i];
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		flag = 1;
    		maxf += flow;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		cur[u] = head[i];
    		int v = edge[i].v;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", a + i);
    	ans = 0;
    	for (int i = 1; i <= n; i++) {
    		dp[i] = 1;
    		for (int j = 1; j < i; j++) {
    			if (a[i] >= a[j]) dp[i] = max(dp[i], dp[j] + 1);
    		}
    		ans = max(ans, dp[i]);
    	}
    	printf("%d\n", ans);
    	memset(head, -1, sizeof head);
    	st = 0, ed = n + n + 5;
    	for (int i = 1; i <= n; i++) {
    		addedge(i, i + n, 1), addedge(i + n, i, 0);
    		if (dp[i] == 1) addedge(st, i, 1), addedge(i, st, 0);
    		if (dp[i] == ans) addedge(i + n, ed, 1), addedge(ed, i + n, 0);
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j < i; j++) {
    			if (a[j] <= a[i] && dp[j] + 1 == dp[i]) addedge(j + n, i, 1), addedge(i, j + n, 0);
    		}
    	}
    	dinic(st, ed);
    	printf("%d\n", maxf);
    	for (int i = 0; i <= cnt; i += 2) edge[i].w += edge[i ^ 1].w, edge[i ^ 1].w = 0;
    	addedge(1, n + 1, inf), addedge(n + 1, 1, 0);
    	addedge(st, 1, inf), addedge(1, st, 0);
    	if (dp[n] == ans) addedge(n, n * 2, inf), addedge(n * 2, n, 0), addedge(n * 2, ed, inf), addedge(ed, n * 2, 0);
    	maxf = 0;
    	dinic(st, ed);
    	printf("%d\n", maxf);
    	return 0;
    }
    
    

    \(7.\)洛谷P2774 方格取数问题
    分析:
    这道题逆着思考就比较容易了:删去权值和最小的点集,使得剩下的点不互斥。
    考虑 最大流\(=\)最小割
    我们先将给定的图按题意进行黑白染色,那么样例染色之后就如下图:

    so,我们让源点与黑点建流量为黑点点权的边,白点与汇点建流量为白点点权的边,黑点与其相邻的白点建流量为\(inf\)的边。那么我们会得到这样的图:

    我们对这样的图跑最大流,就意味着每一条从源点到汇点的路径中,我们会删掉一条非\(inf\)边,删掉的这条边恰好是最大流量又恰好是最小割(删掉的点权值最小)。
    所以答案就显然了,\(ans=sum-maxflow\)

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 100 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, it, sum, f[maxn][maxn], pic[maxn][maxn];
    int cnt, head[maxn * maxn];
    int st, ed, maxf, flag, cur[maxn * maxn], vis[maxn * maxn], deep[maxn * maxn];
    
    struct Graph {
    	int v, w, next;
    } edge[maxn * maxn];
    
    void addedge(int u, int v, int w) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int get(int x, int y) {
    	return m * (x - 1) + y;
    }
    
    int bfs(int st, int ed) {
    	for (int i = 0; i <= ed; i++) cur[i] = head[i], vis[i] = 0, deep[i] = inf;
    	queue<int> q;
    	q.push(st);
    	deep[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && deep[v] > deep[u] + 1) {
    				deep[v] = deep[u] + 1;
    				if (!vis[v]) {
    					q.push(v);
    					vis[v] = 1;
    				}
    			}
    		}
    	}
    	return deep[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		maxf += flow;
    		flag = 1;
    		return flow;
    	}
    	int now = 0, used = 0;
    	for (int i = cur[u]; ~i; i = edge[i].next) {
    		cur[u] = i;
    		int v = edge[i].v;
    		if (edge[i].w && deep[v] == deep[u] + 1) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void dinic(int st, int ed) {
    	while (bfs(st, ed)) {
    		flag = 1;
    		while (flag) {
    			flag = 0;
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= n; i++) 
    		for (int j = 1; j <= m; j++) scanf("%d", &pic[i][j]), sum += pic[i][j];
    	st = 0, ed = n * m + 3;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= m; j++) {
    			if (!f[i - 1][j] && !f[i][j - 1]) addedge(st, get(i, j), pic[i][j]), addedge(get(i, j), st, 0), f[i][j] = 1;
    		}
    	}
    	for (int i = 1; i <= n; i++)
    		for (int j = 1; j <= m; j++) if (!f[i][j])
    			addedge(get(i, j), ed, pic[i][j]), addedge(ed, get(i, j), 0), f[i][j] = 2;
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= m; j++) {
    			if (f[i][j] == 2) continue;
    			it = get(i, j);
    			if (j >= 2) addedge(it, get(i, j - 1), inf), addedge(get(i, j - 1), it, 0);
    			if (j <= m - 1) addedge(it, get(i, j + 1), inf), addedge(get(i, j + 1), it, 0);
    			if (i >= 2) addedge(it, get(i - 1, j), inf), addedge(get(i - 1, j), it, 0);
    			if (i <= n - 1) addedge(it, get(i + 1, j), inf), addedge(get(i + 1, j), it, 0);
    		}
    	}
    	dinic(st, ed);
    	printf("%d\n", sum - maxf);
    	return 0;
    }
    

    \(8.\)洛谷P1251 餐巾计划问题
    分析:
    对于这题我们可以换一个角度来思考:将每天(点)分成早上(入点)和晚上(入点),早上用餐巾。
    建图:
    \(1.\)每天早上都会用掉\(x\)条新餐巾,也就意味着晚上至少会有\(x\)条旧餐巾。
    所以源点向每天晚上建流量为\(x\)费用为\(0\)的边。
    \(2.\)因为每天一定要有\(x\)条新餐巾,所以早上向汇点建流量为\(x\)费用为\(0\)的边。
    \(3.\)每天早上可以购买很多条新餐巾来满足当天条件,所以源点向早上建流量为\(inf\)费用为\(p\)的边。
    \(4.\)对于晚上的旧餐巾有三种转移:
    一:留着不洗放到下一天,即对下一天晚上建容量为\(inf\)费用为\(0\)的边。
    二:慢洗,可以洗很多条旧餐巾,所以向洗好的那天早上建容量为\(inf\)费用为\(s\)的边。
    三:快洗,同上,建容量为\(inf\)费用为\(f\)的边。
    最后跑一遍最小费用最大流即可,最小费用即是答案

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 4000 + 5;
    const int maxm  = 100 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, x, cnt, head[maxn];
    LL a, b, c, d, e, minc, dis[maxn];
    int st, ed, maxf, vis[maxn];
    
    struct Graph {
    	int v, w, next;
    	LL c;
    } edge[maxn << 3];
    
    void addedge(int u, int v, int w, LL c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int spfa(int st, int ed) {
    	for (int i = 0; i <= ed; i++) dis[i] = inf, vis[i] = 0;
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && dis[v] > dis[u] + edge[i].c) {
    				dis[v] = dis[u] + edge[i].c;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return dis[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		vis[ed] = 1;
    	//	maxf += flow;
    		return flow;
    	}
    	vis[u] = 1;
    	int now = 0, used = 0;
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		if ((v == ed || !vis[v]) && edge[i].w && dis[v] == dis[u] + edge[i].c) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			minc += now * edge[i].c;
    		}
    	}
    	return used;
    }
    
    void mcmf(int st, int ed) {
    	while (spfa(st, ed)) {
    		vis[ed] = 1;
    		while (vis[ed]) {
    			memset(vis, 0, sizeof vis);
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	st = 0, ed = n + n + 3;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &x);
    		addedge(i, ed, x, 0), addedge(ed, i, 0, 0);
    		addedge(st, i + n, x, 0), addedge(i + n, st, 0, 0);
    	}
    	scanf("%lld %lld %lld %lld %lld", &a, &b, &c, &d, &e);
    	for (int i = 1; i <= n; i++) {
    		addedge(st, i, inf, a), addedge(i, st, 0, -a);
    		if (i + 1 <= n) addedge(i + n, i + 1 + n, inf, 0), addedge(i + 1 + n, i + n, 0, 0);
    		if (i + d <= n) addedge(i + n, i + d, inf, e), addedge(i + d, i + n, 0, -e);
    		if (i + b <= n) addedge(i + n, i + b, inf, c), addedge(i + b, i + n, 0, -c);
    	}
    	mcmf(st, ed);
    //	printf("%d\n", maxf);
    	printf("%lld\n", minc);
    	return 0;
    }
    

    \(9.\)洛谷P4013 数字梯形问题
    分析:
    先对题目中的三种询问翻译成人话:
    从第一行选\(m\)个点,第一行的每个点选一次,再选\(m\)条路径走到梯形底,求得到的最大价值。
    \(I.\)梯形中的每个点只选一次组成的\(m\)条路径
    \(II.\)梯形中的每个点可以选无数次,但不能有路径相交(边只能选一次)
    \(III.\)无限制
    对于\(I\),点只能取一次的情况,将点拆成入点与出点,入点与出点间流量为\(1\)即可满足条件。源点到第一层的点和最后一层的点到汇点的流量均为\(1\),费用分别为\(0\)与负的点权。
    对于\(II\),点可以无限取所以不必拆点了,由于边只能选一次,那么就将边的流量设为\(1\)就好了。源点到第一层的点流量为\(1\),最后一层的点到汇点流量为\(inf\),费用分别为\(0\)与负的点权。
    对于\(III\),边流量都设为\(inf\)即可。
    点与点之间的费用设为负的跑最大费用最大流即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 5000 + 5;
    const int maxm  = 50 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, now, a[maxm][maxm], b[maxm][maxm];
    int cnt, head[maxn];
    int st, ed, maxf, minc, dis[maxn], vis[maxn];
    
    struct Graph {
    	int v, w, c, next;
    } edge[maxn << 2];
    
    void addedge(int u, int v, int w, int c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    void add(int u, int v, int w, int c) {
    	addedge(u, v, w, c);
    	addedge(v, u, 0, -c);
    }
    
    int spfa(int st, int ed) {
    	for (int i = 0; i <= ed; i++) dis[i] = inf, vis[i] = 0;
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && dis[v] > dis[u] + edge[i].c) {
    				dis[v] = dis[u] + edge[i].c;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return dis[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		vis[ed] = 1;
    	//	maxf += flow;
    		return flow;
    	}
    	vis[u] = 1;
    	int now = 0, used = 0;
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		if ((v == ed || !vis[v]) && edge[i].w && dis[v] == dis[u] + edge[i].c) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			minc += now * edge[i].c;
    		}
    	}
    	return used;
    }
    
    void mcmf(int st, int ed) {
    	while (spfa(st, ed)) {
    		vis[ed] = 1;
    		while (vis[ed]) {
    			memset(vis, 0, sizeof vis);
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	for (int i = 1; i <= m; i++) {
    		for (int j = 1; j <= i + n - 1; j++) {
    			scanf("%d", &a[i][j]);
    			b[i][j] = ++now;
    		}
    	}
    	st = 0, ed = 5000;
    	memset(head, -1, sizeof head); 
    	for (int i = 1; i <= n; i++) add(st, b[1][i], 1, 0);
    	for (int i = 1; i <= m; i++) {
    		for (int j = 1; j <= i + n - 1; j++) {
    			add(b[i][j], b[i][j] + 2500, 1, -a[i][j]);
    			if (i != m) add(b[i][j] + 2500, b[i + 1][j], 1, 0), add(b[i][j] + 2500, b[i + 1][j + 1], 1, 0);
    		}
    	}
    	for (int i = 1; i <= n + m - 1; i++) add(b[m][i] + 2500, ed, 1, 0);
    	mcmf(st, ed);
    	printf("%d\n", -minc);
    	minc = 0, cnt = 0, memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) add(st, b[1][i], 1, 0); 
    	for (int i = 1; i <= m; i++) {
    		for (int j = 1; j <= i + n - 1; j++) {
    			if (i != m) add(b[i][j], b[i + 1][j], 1, -a[i][j]), add(b[i][j], b[i + 1][j + 1], 1, -a[i][j]);
    		}
    	}
    	for (int i = 1; i <= n + m - 1; i++) add(b[m][i], ed, inf, -a[m][i]);
    	mcmf(st, ed);
    	printf("%d\n", -minc);
    	minc = 0, cnt = 0, memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) add(st, b[1][i], 1, 0);
    	for (int i = 1; i <= m; i++) {
    		for (int j = 1; j <= i + n - 1; j++) {
    			if (i != m) add(b[i][j], b[i + 1][j], inf, -a[i][j]), add(b[i][j], b[i + 1][j + 1], inf, -a[i][j]);
    		}
    	}
    	for (int i = 1; i <= n + m - 1; i++) add(b[m][i], ed, inf, -a[m][i]);
    	mcmf(st, ed);
    	printf("%d\n", -minc);
    	return 0;
    }
    

    \(10.\)洛谷P4015 运输问题
    分析:
    类似二分匹配,只不过匹配带权了。
    \(a\)仓库作为左部,\(b\)仓库作为右部,源点\(\rightarrow a\)建流量为\(a\)仓库货物量,费用为\(0\)的边,\(b \rightarrow\)汇点建流量为\(b\)仓库所需货物量,费用为\(0\)
    \(a\)仓库与\(b\)仓库建流量为\(inf\)费用为相应费用的边。
    跑一遍最小费用最大流即可得第一个询问的答案。
    第二个询问是最大费用最大流,那么我们将原先的正向边与反向边的费用取反,并把流量回归再跑最小费用最大流,最后将答案取反就是最大费用。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 100 + 5;
    const int maxm  = 50000 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, w;
    int st, ed, cnt, vis[maxn << 1], head[maxn << 1];
    LL dis[maxn << 1], mincost;
    
    struct Graph {
    	int v, w, c, next;
    } edge[maxm * maxn << 1];
    
    void addedge(int u, int v, int w, int c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int spfa(int st, int ed) {
    	for (int i = 0; i <= n + m + 5; i++) dis[i] = 1e17;
    	memset(vis, 0, sizeof vis);
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v, c = edge[i].c;
    			if (edge[i].w && dis[v] > dis[u] + c) {
    				dis[v] = dis[u] + c;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return dis[ed] != 1e17;
    }
    
    int dfs(int u, int flow, int ed) {
    	if (u == ed) {
    		vis[ed] = 1;
    		return flow;
    	}
    	vis[u] = 1;
    	int now = 0, used = 0;
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		if ((!vis[v] || v == ed) && dis[v] == dis[u] + edge[i].c && edge[i].w) {
    			now = dfs(v, min(flow - used, edge[i].w), ed);
    			if (!now) continue;
    			used += now;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			mincost += edge[i].c * now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void mcmf(int st, int ed) {
    	while (spfa(st, ed)) {
    		vis[ed] = 1;
    		while (vis[ed]) {
    			memset(vis, 0, sizeof vis);
    			dfs(st, inf, ed);
    		}
    	}
    }
    
    int main() {
    	scanf("%d %d", &n, &m);
    	st = 0, ed = n + m + 1;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d", &w);
    		addedge(st, i, w, 0), addedge(i, st, 0, 0);
    	}
    	for (int i = 1; i <= m; i++) {
    		scanf("%d", &w);
    		addedge(i + n, ed, w, 0), addedge(ed, i + n, 0, 0);
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= m; j++) {
    			scanf("%d", &w);
    			addedge(i, j + n, inf, w), addedge(j + n, i, 0, -w);
    		}
    	}
    	mcmf(st, ed);
    	printf("%lld\n", mincost);
    	for (int i = 0; i <= cnt; i += 2) {
    		edge[i].w += edge[i ^ 1].w;
    		edge[i ^ 1].w = 0;
    		edge[i].c = -edge[i].c;
    		edge[i ^ 1].c = -edge[i ^ 1].c;
    	}
    	mincost = 0;
    	mcmf(st, ed);
    	printf("%lld\n", -mincost);
    	return 0;
    }
    
    

    \(11.\)洛谷P4014 分配问题
    \(10\)相同是二分图带权匹配问题,输出最小以及最大费用。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 100 + 5;
    const int maxm  = 50000 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, w;
    int st, ed, cnt, dis[maxn << 1], vis[maxn << 1], head[maxn << 1];
    int maxflow, mincost;
    
    struct Graph {
    	int v, w, c, next;
    } edge[maxm * maxn << 1];
    
    void addedge(int u, int v, int w, int c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int spfa(int st, int ed) {
    	memset(dis, inf, sizeof dis);
    	memset(vis, 0, sizeof vis);
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v, c = edge[i].c;
    			if (dis[v] > dis[u] + c && edge[i].w) {
    				dis[v] = dis[u] + c;
    				if (!vis[v]) {
    					q.push(v);
    					vis[v] = 1;
    				}
    			}
    		}
    	}
    	if (dis[ed] != inf) return 1;
    	return 0;
    }
    
    int dfs(int x, int flow, int ed) {
    	if (x == ed) {
    		vis[ed] = 1;
    		maxflow += flow;
    		return flow;
    	}
    	vis[x] = 1;
    	int now = 0, used = 0;
    	for (int i = head[x]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		if ((!vis[v] || v == ed) && edge[i].w && dis[v] == dis[x] + edge[i].c) {
    			now = dfs(v, min(flow - used, edge[i].w), ed);
    			if (now != 0) mincost += edge[i].c * now, edge[i].w -= now, edge[i ^ 1].w += now, used += now;
    			if (used == flow) break;
    		}
    	}
    	return used;
    }
    
    void mcmf(int st, int ed) {
    	while (spfa(st, ed)) {
    		vis[ed] = 1;
    		while (vis[ed]) {
    			memset(vis, 0, sizeof vis);
    			dfs(st, inf, ed);	
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	st = 0, ed = n + n + 1;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		addedge(st, i, 1, 0), addedge(i, st, 0, 0);
    		addedge(i + n, ed, 1, 0), addedge(ed, i + n, 0, 0);
    		for (int j = 1; j <= n; j++) {
    			scanf("%d", &w);
    			addedge(i, j + n, 1, w), addedge(j + n, i, 0, -w);
    		}
    	}
    	mincost = 0;
    	mcmf(st, ed);
    	printf("%d\n", mincost);
    	for (int i = 0; i <= cnt; i += 2) edge[i].w += edge[i ^ 1].w, edge[i ^ 1].w = 0, edge[i].c = -edge[i].c, edge[i ^ 1].c = -edge[i ^ 1].c;
    	mincost = 0;
    	mcmf(st, ed);
    	printf("%d\n", -mincost);
    	return 0;
    }
    
    

    \(12.\)洛谷P4016 负载平衡问题
    分析:最后要求所有仓库的货物量一样,那么每个仓库的最终货物量就是货物总和的平均数\(average\)
    设每个仓库货物量\(w[i]\)
    如果一个仓库的货物量\(> ave\),说明这个仓库要流出流量使得别的仓库到达平均值。
    如果一个仓库的货物量\(< ave\),说明这个仓库要流入到达平均值。
    所以最大流就是流入\(< ave\)的仓库的流量。
    从源点对货物量\(> ave\)的仓库建流量为\(w[i]-ave\),费用为\(0\)的边。
    从货物量\(< ave\)的仓库建流量为\(ave-w[i]\),费用为\(0\)的边。
    按照题意对每个货物的两端建流量为\(inf\),费用为\(1\)的边。
    跑一遍最小费用最大流即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 100 + 5;
    const int maxm  = 50000 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, ave, a[maxn];
    int st, ed, cnt, dis[maxn], vis[maxn], head[maxn];
    int maxflow, mincost;
    
    struct node {
    	int u, e;
    } pre[maxn << 1];
    
    struct Graph {
    	int v, w, c, next;
    } edge[maxn * maxn << 2];
    
    void addedge(int u, int v, int w, int c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    int spfa(int st, int ed) {
    	memset(dis, inf, sizeof dis);
    	memset(vis, 0, sizeof vis);
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v, c = edge[i].c;
    			if (dis[v] > dis[u] + c && edge[i].w) {
    				dis[v] = dis[u] + c;
    				pre[v].u = u;
    				pre[v].e = i;
    				if (!vis[v]) {
    					q.push(v);
    					vis[v] = 1;
    				}
    			}
    		}
    	}
    	if (dis[ed] != inf) return dis[ed];
    	return 0;
    }
    
    void mcmf(int st, int ed) {
    	int minw, res;
    	while (res = spfa(st, ed)) {
    		minw = inf;
    		for (int i = ed; i != st; i = pre[i].u) minw = min(minw, edge[pre[i].e].w);
    		for (int i = ed; i != st; i = pre[i].u) edge[pre[i].e].w -= minw, edge[pre[i].e ^ 1].w += minw;
    		maxflow += minw;
    		mincost += minw * res; 
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) scanf("%d", a + i), ave += a[i];
    	ave /= n;
    	st = 0, ed = n + 1;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		if (a[i] > ave) addedge(st, i, a[i] - ave, 0), addedge(i, st, 0, 0);
    		else addedge(i, ed, ave - a[i], 0), addedge(ed, i, 0, 0);
    		if (i != 1 && i != n) {
    			addedge(i, i + 1, inf, 1), addedge(i + 1, i, 0, -1);
    			addedge(i, i - 1, inf, 1), addedge(i - 1, i, 0, -1);
    		}
    	}
    	addedge(1, n, inf, 1), addedge(n, 1, 0, -1);
    	addedge(1, 2, inf, 1), addedge(2, 1, 0, -1);
    	addedge(n, 1, inf, 1), addedge(1, n, 0, -1);
    	addedge(n, n - 1, inf, 1), addedge(n - 1, n, 0, -1);
    	mcmf(st, ed);
    	printf("%d\n", mincost);
    	return 0;
    }
    
    
    

    \(13.\)洛谷P2770 航空路线问题
    分析:
    实际上题目就是让我们求两条从\(1\)\(n\)的路径,并且两条路径没有重点(除了\(1\)\(n\))。
    限制一个点的选取数量,那么我们队这个点进行拆点即可,入点与出点流量为\(1\)
    特别的\(1\)\(n\)号点流量为\(2\),因为可以用两次。
    两点之间有边\(<u,v>\),那么就让\(u\)的出点连向\(v\)的入点即可,流量为\(1\)
    建好图后,跑一遍最大流,有两种情况是有解的。
    \(I.\)最大流为2,肯定有解
    \(II.\)最大流为1,且有\(1\)直通\(n\)的路径。(特判)
    输出路径两次\(dfs\)即可,每次\(dfs\)残量为\(0\)的边(记得\(break\)),\(dfs\)一次能得到一条路径,再将第二次倒序输出并且不输出\(n\)号点即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    
    typedef long long LL;
    typedef unsigned long long ULL;
    typedef pair<int, int> pii;
    
    const int maxn  = 200 + 5;
    const int maxm  = 50 + 5;
    const int inf   = 0x3f3f3f3f;
    
    int n, m, u, v, flag;
    int cnt, head[maxn];
    int st, ed, maxf, minc, dis[maxn], vis[maxn];
    string pic[111], str, ptr;
    map<string, int> p;
    vector<int> ve;
    
    struct Graph {
    	int v, w, c, next;
    } edge[maxn << 3];
    
    void addedge(int u, int v, int w, LL c) {
    	edge[cnt].v = v;
    	edge[cnt].w = w;
    	edge[cnt].c = c;
    	edge[cnt].next = head[u];
    	head[u] = cnt++;
    }
    
    void add(int u, int v, int w, int c) {
    	addedge(u, v, w, c);
    	addedge(v, u, 0, -c);
    }
    
    int spfa(int st, int ed) {
    	for (int i = 0; i <= ed; i++) dis[i] = inf, vis[i] = 0;
    	queue<int> q;
    	q.push(st);
    	dis[st] = 0;
    	while (!q.empty()) {
    		int u = q.front();
    		q.pop();
    		vis[u] = 0;
    		for (int i = head[u]; ~i; i = edge[i].next) {
    			int v = edge[i].v;
    			if (edge[i].w && dis[v] > dis[u] + edge[i].c) {
    				dis[v] = dis[u] + edge[i].c;
    				if (!vis[v]) {
    					vis[v] = 1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return dis[ed] != inf;
    }
    
    int dfs(int u, int ed, int flow) {
    	if (u == ed) {
    		vis[ed] = 1;
    		maxf += flow;
    		return flow;
    	}
    	vis[u] = 1;
    	int now = 0, used = 0;
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		int v = edge[i].v;
    		if ((v == ed || !vis[v]) && edge[i].w && dis[v] == dis[u] + edge[i].c) {
    			now = dfs(v, ed, min(flow - used, edge[i].w));
    			if (!now) continue;
    			edge[i].w -= now;
    			edge[i ^ 1].w += now;
    			used += now;
    			minc += now * edge[i].c;
    		}
    	}
    	return used;
    }
    
    void mcmf(int st, int ed) {
    	while (spfa(st, ed)) {
    		vis[ed] = 1;
    		while (vis[ed]) {
    			memset(vis, 0, sizeof vis);
    			dfs(st, ed, inf);
    		}
    	}
    }
    
    void dfs1(int u, int ed) {
    	ve.pb(u);
    	if (u == ed) {
    		return ;
    	}
    	for (int i = head[u]; ~i; i = edge[i].next) {
    		if (!edge[i].w) {
    			dfs1(edge[i].v + n, ed);
    			edge[i].w = 1;
    			break;
    		}
    	}
    }
    
    int main() {
    	cin >> n >> m;
    	memset(head, -1, sizeof head);
    	for (int i = 1; i <= n; i++) {
    		cin >> pic[i];
    		p[pic[i]] = i;
    		if (i == 1 || i == n) add(i, i + n, 2, -1);
    		else add(i, i + n, 1, -1);
    	}
    	for (int i = 1; i <= m; i++) {
    		cin >> str >> ptr;
    		u = p[str], v = p[ptr];
    		if (u > v) swap(u, v);
    		if (u == 1 && v == n) flag = 1;
    		add(u + n, v, 1, 0);
    	}
    	st = 1, ed = n << 1;
    	mcmf(st, ed);
    	if (maxf == 1 && flag) {
    		cout << 2 << '\n' << pic[1] << '\n' << pic[n] << '\n' << pic[1] << '\n';
    	} else if (maxf == 2){
    		cout << -minc - 2 << '\n';
    		ve.clear();
    		dfs1(1 + n, n + n);
    		for (auto it : ve) if (it > n) cout << pic[it - n] << '\n';
    		ve.clear();
    		dfs1(1 + n, n + n);
    		reverse(ve.begin(), ve.end());
    		for (auto it : ve) if (it > n && it != n * 2) cout << pic[it - n] << '\n';
    	} else {
    		cout << "No Solution!" << '\n';
    	}
    	return 0;
    }
    

    咕咕咕

  • 相关阅读:
    在win7 64位上安装VS2015的问题汇总
    关于C#类的深拷贝的问题
    线程、进程
    c#日志 log4net
    C#常识
    Tribon数据抽取的一些心得
    Java Web相关课程学习笔记
    过滤器、监听器、拦截器的区别
    SHH架构中几个配置文件解释 applicationContext.xml web.xml struts.xml
    vue关于动态增加路由页面
  • 原文地址:https://www.cnblogs.com/ChaseNo1/p/11503970.html
Copyright © 2020-2023  润新知