网络流24题

1. 餐巾计划问题

【洛谷1251】餐巾计划问题

题面

题目描述
一个餐厅在相继的 N N N 天里,每天需用的餐巾数不尽相同。假设第 i i i 天需要 r i r_i ri 块餐巾( i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 p p p 分;或者把旧餐巾送到快洗部,洗一块需 m m m 天,其费用为 f f f 分;或者送到慢洗部,洗一块需 n n n 天( n > m n>m n>m),其费用为 s s s 分( s < f s<f s<f)。
每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。
试设计一个算法为餐厅合理地安排好 N N N 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

输入格式
由标准输入提供输入数据。文件第 1 1 1 行有 1 1 1 个正整数 N N N,代表要安排餐巾使用计划的天数。
接下来的 N N N 行是餐厅在相继的 N N N 天里,每天需用的餐巾数。
最后一行包含 5 5 5 个正整数 p , m , f , n , s p,m,f,n,s p,m,f,n,s p p p 是每块新餐巾的费用; m m m 是快洗部洗一块餐巾需用天数; f f f 是快洗部洗一块餐巾需要的费用; n n n 是慢洗部洗一块餐巾需用天数; s s s 是慢洗部洗一块餐巾需要的费用。

输出格式
将餐厅在相继的 N N N 天里使用餐巾的最小总花费输出。

输入输出样例
输入 #1
3
1 7 5
11 2 2 3 1
输出 #1
134

数据范围
N ≤ 2000 ; r i ≤ 10000000 ; p , f , s ≤ 10000 N\leq2000; r_i\leq10000000; p,f,s\leq10000 N2000;ri10000000;p,f,s10000


分析

显然得费用流。

最小费用最大流中, “最大流”这个条件就可以用来使你的流满足题目的要求,“最小费用”便用来求,满足要求的前提下的最小代价。
此题中,要求是第 i i i天需要有 r i r_i ri块餐巾,因此我们只要把每天往 T T T连一条流量为 r i r_i ri,费用为 0 0 0的边(①),这样一来,由于程序先满足最大流,所以每条边都会满流,也就满足了题目的要求。

注意到,我们要区分“干净的餐巾”和“脏的餐巾”,所以我们将一天拆成两个点,一个代表早晨(干净餐巾),一个代表晚上(脏餐巾)。

于是题目的限制就是,每天早上需要有 r i r_i ri块餐巾,因此将①变为:每天早上向 T T T连一条流量为 r i r_i ri,费用为 0 0 0的边。

网络流中,源点 S S S可以向外无限发送流量,也就是说,只要有机会就会发出流量,那么这个性质可以用来与题目的“客观条件”相结合。
例如,本题中,每天晚上会产生 r i r_i ri块餐巾,这是客观事实,不能改变,所以我们将 S S S向每天晚上连一条容量为 r i r_i ri,费用为 0 0 0的边,这些边目前来看一定会满流,所以满足了客观条件。

剩下的问题就好办了:

  • S S S向每天早上连一条容量为 + ∞ +\infty +,费用为 p p p的边,代表买新餐巾;
  • i i i天晚上向第 i + 1 i + 1 i+1天晚上连一条容量为 + ∞ +\infty +,费用为 0 0 0的边,代表把脏餐巾留到第二天晚上;
  • i i i天晚上向第 i + n i + n i+n天晚上连一条容量为 + ∞ +\infty +,费用为 s s s的边,代表把餐巾送到慢洗部洗;
  • i i i天晚上向第 i + m i + m i+m天晚上连一条容量为 + ∞ +\infty +,费用为 f f f的边,代表把餐巾送到快洗部洗;

这些边的容量都是 + ∞ +\infty +,意味着这些边不会影响之前那些边的满流,于是题目条件得到了保证。再跑最小费用最大流即可。

代码

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>

typedef long long LL;

const int MAXN = 2000;
const LL INF = 1ll << 60;
const int intINF = 0x3f3f3f3f;

namespace Dinic {
	int N;
	struct Edge {
		int v, cap, cost, nxt;
	}E[MAXN * 50 + 5];
	int EdgeCnt, Adj[MAXN * 3 + 5];
	
	void Init(int n) {
		N = n, EdgeCnt = 0;
		memset(Adj, -1, sizeof Adj);
	}
	
	void AddEdge(int u, int v, int cap, int cost) {
		E[EdgeCnt] = (Edge){v, cap, cost, Adj[u]}, Adj[u] = EdgeCnt++;
		E[EdgeCnt] = (Edge){u, 0,  -cost, Adj[v]}, Adj[v] = EdgeCnt++;
	}
	
	LL Dist[MAXN * 3 + 5];
	bool Vis[MAXN * 3 + 5];
	int PreN[MAXN * 3 + 5], PreE[MAXN * 3 + 5];
	
	bool SPFA(int S, int T) {
		std::fill(Vis + 1, Vis + N + 1, 0);
		std::fill(PreN + 1, PreN + N + 1, -1);
		std::fill(PreE + 1, PreE + N + 1, -1);
		std::fill(Dist + 1, Dist + N + 1, INF);
		std::queue<int> Q;
		Q.push(S), Dist[S] = 0, Vis[S] = true;
		while (!Q.empty()) {
			int u = Q.front(); Q.pop(); Vis[u] = false;
			for (int i = Adj[u]; ~i; i = E[i].nxt) {
				int v = E[i].v, c = E[i].cap, w = E[i].cost;
				if (c && Dist[v] > Dist[u] + w) {
					Dist[v] = Dist[u] + w;
					PreN[v] = u, PreE[v] = i;
					if (!Vis[v])
						Q.push(v), Vis[v] = true;
				}
			}
		}
		return Dist[T] < INF;
	}
	
	LL MinCost(int S, int T) {
		LL ret = 0;
		while (SPFA(S, T)) {
			LL go = INF;
			for (int u = T; u != S; u = PreN[u])
				go = std::min(go, (LL)E[PreE[u]].cap);
			for (int u = T; u != S; u = PreN[u]) {
				ret += go * E[PreE[u]].cost;
				E[PreE[u]].cap -= go;
				E[PreE[u] ^ 1].cap += go;
			}
		}
		return ret;
	}
}

int N, p, n, m, s, f, R[MAXN + 5];

int main() {
	scanf("%d", &N);
	Dinic::Init(2 * N + 2);
	int S = 2 * N + 1, T = 2 * N + 2;
	for (int i = 1; i <= N; i++)
		scanf("%d", &R[i]);
	scanf("%d%d%d%d%d", &p, &m, &f, &n, &s);
	for (int i = 1; i <= N; i++) {
		Dinic::AddEdge(i, T, R[i], 0);
		Dinic::AddEdge(S, i, intINF, p);
		Dinic::AddEdge(S, i + N, R[i], 0);
		if (i + m <= N) Dinic::AddEdge(i + N, i + m, intINF, f);
		if (i + n <= N) Dinic::AddEdge(i + N, i + n, intINF, s);
		if (i + 1 <= N) Dinic::AddEdge(i + N, i + 1 + N, intINF, 0);
	}
	printf("%lld", Dinic::MinCost(S, T));
	return 0;
}

2.[CTSC1999]家园

题面

【洛谷2754】[CTSC1999]家园
题目描述
由于人类对自然资源的消耗,人们意识到大约在 2300 年之后,地球就不能再居住了。于是在月球上建立了新的绿地,以便在需要时移民。令人意想不到的是,2177 年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。
现有 n n n 个太空站位于地球与月球之间,且有 m m m 艘公共交通太空船在其间来回穿梭。每个太空站可容纳无限多的人,而每艘太空船 i i i 只可容纳 H i H_i Hi 个人。每艘太空船将周期性地停靠一系列的太空站,例如: ( 1 , 3 , 4 ) (1,3,4) (1,3,4) 表示该太空船将周期性地停靠太空站 134134134 ⋯ 134134134\cdots 134134134。每一艘太空船从一个太空站驶往任一太空站耗时均为 1 1 1。人们只能在太空船停靠太空站(或月球、地球)时上、下船。
初始时所有人全在地球上,太空船全在初始站。试设计一个算法,找出让所有人尽快地全部转移到月球上的运输方案。
对于给定的太空船的信息,找到让所有人尽快地全部转移到月球上的运输方案。

输入格式
1 1 1 行有 3 3 3 个正整数 n n n(太空站个数), m m m(太空船个数)和 k k k(需要运送的地球上的人的个数)。
接下来的 m m m 行给出太空船的信息。第 i + 1 i+1 i+1 行说明太空船 p i p_i pi。第 1 1 1 个数表示 p i p_i pi 可容纳的人数 H p i H_{p_i} Hpi;第 2 2 2 个数表示 p i p_i pi 一个周期停靠的太空站个数 r r r;随后 r r r 个数是停靠的太空站的编号,地球用 0 0 0 表示,月球用 − 1 -1 1 表示。
时刻 0 0 0 时,所有太空船都在初始站,然后开始运行。在时刻 1 , 2 , 3 ⋯ 1,2,3\cdots 1,2,3等正点时刻各艘太空船停靠相应的太空站。人只有在 0 , 1 , 2 ⋯ 0,1,2\cdots 0,1,2等正点时刻才能上下太空船。

输出格式
程序运行结束时,将全部人员安全转移所需的时间输出。如果问题无解,则输出 0。

输入输出样例
输入 #1
2 2 1
1 3 0 1 2
1 3 1 2 -1
输出 #1
5

数据范围
n ≤ 13 ; m ≤ 20 ; k ≤ 50 ; 1 ≤ r ≤ n + 2 n\leq13; m\leq20; k\leq50;1 \leq r \leq n + 2 n13;m20;k50;1rn+2


分析

本题的难点是,有“人”“时刻”“飞船”“停靠站”这四个变量,然而网络流无法分开承担这四个变量,因此我们必须使某些变量用网络流的某一个参数承担。于是考虑,将时间和停靠站的信息同时给结点,对于时刻类的问题,有一个很经典的做法:按时刻建立分层图,实施降维打击。至于人就用流量刻画,飞船就用站点之间的边来刻画了。

枚举一个运输时间 t t t,用最大流判断是否能输送 k k k个人。将每个停靠站 i i i拆成 t t t个点,分别代表 0 0 0时刻的 i i i 1 1 1时刻的 i i i 2 2 2时刻的 i i i……然后按照飞船的往返规律和运载量连边即可。注意 x x x时间的 i i i需向 x + 1 x + 1 x+1时间的 i i i连一条容量为 + ∞ +\infty +的边,因为人可以呆在那里等一天。
按照上一题的思路,因为人都要送达这一题目要求(同时也是客观条件),所以我们将 S S S 0 0 0时刻的地球连一条容量为 k k k的边,将 t t t时刻的月球向 T T T连一条边。

最后的图大概长这样:
建图
无解就是用并查集判一下连通性即可。

代码

每次只会加边不会少边,所以不用每次重新建图,但是要注意,后来每次都要加上之前的流量。
并且新建 S S S T T T也是不必要的,判断 0 0 0时刻地球到 t t t时刻月球的流量大于等于 k k k即可。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>

const int MAXN = 13;
const int MAXM = 20;
const int MAXK = 50;
const int MAXE = 10000;
const int INF = 0x3f3f3f3f;

namespace Dinic {
	struct Edge {
		int v, nxt, cap;
	}E[MAXE * 2 + 5];
	int EdgeCnt, Adj[MAXE + 5], Cur[MAXE + 5], N;
	
	void Init() {
		EdgeCnt = 0;
		memset(Adj, -1, sizeof Adj);
	}

	void AddEdge(int u, int v, int c) {
		E[EdgeCnt] = (Edge){v, Adj[u], c}, Adj[u] = EdgeCnt++;
		E[EdgeCnt] = (Edge){u, Adj[v], 0}, Adj[v] = EdgeCnt++;
	}
	
	int Dist[MAXE + 5];
	
	bool Bfs(int S, int T) {
		std::queue<int> Q;
		std::fill(Dist + 1, Dist + N + 1, INF);
		Dist[S] = 0, Q.push(S);
		while (!Q.empty()) {
			int u = Q.front(); Q.pop();
			for (int i = Adj[u]; ~i; i = E[i].nxt) {
				int v = E[i].v, c = E[i].cap;
				if (c && Dist[v] > Dist[u] + 1)
					Dist[v] = Dist[u] + 1, Q.push(v);
			}
		}
		return Dist[T] < INF;
	}
	
	int Dfs(int u, int T, int flow) {
		if (u == T)
			return flow;
		int del = 0, go = 0;
		for (int &i = Cur[u]; ~i && go < flow; i = E[i].nxt) {
			int v = E[i].v, c = E[i].cap;
			if (Dist[v] != Dist[u] + 1)
				continue;
			if (del = Dfs(v, T, std::min(flow, c)))
				go += del, E[i].cap -= del, E[i ^ 1].cap += del;
		}
		return go;
	}
	
	int MaxFlow(int S, int T) {
		int ret = 0;
		while (Bfs(S, T)) {
			for (int i = 1; i <= N; i++)
				Cur[i] = Adj[i];
			ret += Dfs(S, T, INF);
		}
		return ret;
	}
}

int Fa[MAXN + 5];

int Find(int x) {
	return Fa[x] == x ? x : Fa[x] = Find(Fa[x]);
}

int N, M, K;
int H[MAXM + 5], S[MAXM + 5][MAXN + 5], P[MAXM + 5];

int ID(int i, int t) { // 第i个站的时刻t的那个点的编号
	return i + t * (N + 2);
}

int main() {
	scanf("%d%d%d", &N, &M, &K);
	for (int i = 1; i <= N + 2; i++)
		Fa[i] = i;
	for (int i = 1; i <= M; i++) {
		scanf("%d%d", &H[i], &P[i]);
		for (int j = 1; j <= P[i]; j++) {
			scanf("%d", &S[i][j]), S[i][j] += 2; // 地球 2 月球 1
			if (j > 1)
				Fa[Find(S[i][j])] = Find(S[i][j - 1]);
		}
	}
	if (Find(1) != Find(2))
		return puts("0"), 0;
	Dinic::Init();
	int T = 1, cur = 0;
	while (1) {
		Dinic::N = ID(N + 2, T);
		for (int i = 1; i <= N + 2; i++)
			Dinic::AddEdge(ID(i, T - 1), ID(i, T), INF);
		for (int i = 1; i <= M; i++) {
			int x = S[i][(T - 1) % P[i] + 1], y = S[i][T % P[i] + 1];
			Dinic::AddEdge(ID(x, T - 1), ID(y, T), H[i]);
		}
		cur += Dinic::MaxFlow(ID(2, 0), ID(1, T)); // 注意这里
		if (cur >= K)
			return printf("%d", T), 0;
		T++;
	}
	return 0;
}

3.航空路线问题

题面

题目描述
给定一张航空图,图中顶点代表城市,边代表两城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
(1) 从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
(2) 除起点城市外,任何城市只能访问一次。
对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。

输入格式
1 1 1 行有 2 2 2 个正整数 N N N V V V N N N 表示城市数, V V V 表示直飞航线数。
接下来的 N N N 行中每一行是一个城市名,可乘飞机访问这些城市。城市名出现的顺序是从西向东。也就是说,设 i , j i,j i,j 是城市表列中城市出现的顺序,当 i > j i>j i>j 时,表示城市 i i i 在城市 j j j 的东边,而且不会有 2 2 2 个城市在同一条经线上。城市名是一个长度不超过 15 15 15 的字符串,串中的字符可以是字母或阿拉伯数字。例如,AGR34BEL4
再接下来的 V V V 行中,每行有 2 2 2 个城市名,中间用空格隔开,如 city1 city2 表示 city 1 \text{city}_1 city1 city 2 \text{city}_2 city2 有一条直通航线,从 city 2 \text{city}_2 city2 city 1 \text{city}_1 city1 也有一条直通航线。

输出格式
1 1 1 行是旅行路线中所访问的城市总数 M M M。 接下来的 M + 1 M+1 M1 行是旅行路线的城市名,每行写 1 1 1 个城市名。首先是出发城市名,然后按访问顺序列出其它城市名。 注意,最后 1 1 1 行(终点城市)的城市名必然是出发城市名。如果问题无解,则输出No Solution!

输入输出样例
输入 #1
8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary
输出 #1
7
Vancouver
Edmonton
Montreal
Halifax
Toronto
Winnipeg
Calgary
Vancouver

数据范围
N < 100 N<100 N<100


分析

想了半天怎么找自西向东的路径,最后发现只连自西向东的边就行了= =

然后就简单了,这种限制次数的题,就把次数作为流,相应的点就拆点即可
把每个点 i i i拆成 i 1 i_1 i1 i 2 i_2 i2,然后 i 1 i_1 i1 i 2 i_2 i2连一条容量为 1 1 1,费用为 1 1 1的边,代表这个点只能经过一次;原来的边 ( u , v ) (u, v) (u,v)就变成 ( u 2 , v 1 ) (u_2, v_1) (u2,v1)即可,容量 + ∞ +\infty +,费用 0 0 0

有一个问题是要求从 1 1 1 n n n再返回,我们可以转化为找两条只在 1 1 1 n n n相交的 1 1 1 n n n的路径,那么把建图稍作改动: 1 1 1_1 11 1 2 1_2 12 n 1 n_1 n1 n 2 n_2 n2连容量为 2 2 2,费用为 1 1 1的边。最后判断能否从 1 1 1 n n n发送 2 2 2的流量即可。

还有一个难点是输出方案。方法是直接在残留网络上DFS,走容量为 0 0 0的边就是路径了。

代码

一个细节问题是,要特判 1 1 1 n n n的直达航线。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>

const int MAXN = 100;
const int INF = 0x3f3f3f3f;

int N, M;
std::string S[MAXN + 5];
std::map<std::string, int> ID;

namespace Dinic {
	int N;
	struct Edge {
		int v, cap, cost, nxt;
	}E[MAXN * 50 + 5];
	int EdgeCnt, Adj[MAXN * 3 + 5];
	
	void Init(int n) {
		N = n, EdgeCnt = 0;
		memset(Adj, -1, sizeof Adj);
	}
	
	void AddEdge(int u, int v, int cap, int cost) {
		E[EdgeCnt] = (Edge){v, cap, cost, Adj[u]}, Adj[u] = EdgeCnt++;
		E[EdgeCnt] = (Edge){u, 0,  -cost, Adj[v]}, Adj[v] = EdgeCnt++;
	}
	
	int Dist[MAXN * 3 + 5];
	bool Vis[MAXN * 3 + 5];
	int PreN[MAXN * 3 + 5], PreE[MAXN * 3 + 5];
	
	bool SPFA(int S, int T) {
		std::fill(Vis + 1, Vis + N + 1, 0);
		std::fill(PreN + 1, PreN + N + 1, -1);
		std::fill(PreE + 1, PreE + N + 1, -1);
		std::fill(Dist + 1, Dist + N + 1, INF);
		std::queue<int> Q;
		Q.push(S), Dist[S] = 0, Vis[S] = true;
		while (!Q.empty()) {
			int u = Q.front(); Q.pop(); Vis[u] = false;
			for (int i = Adj[u]; ~i; i = E[i].nxt) {
				int v = E[i].v, c = E[i].cap, w = E[i].cost;
				if (c && Dist[v] > Dist[u] + w) {
					Dist[v] = Dist[u] + w;
					PreN[v] = u, PreE[v] = i;
					if (!Vis[v])
						Q.push(v), Vis[v] = true;
				}
			}
		}
		return Dist[T] < INF;
	}
	
	std::pair<int, int> MinCostMaxFlow(int S, int T) {
		int C = 0, F = 0;
		while (SPFA(S, T)) {
			int go = INF;
			for (int u = T; u != S; u = PreN[u])
				go = std::min(go, (int)E[PreE[u]].cap);
			F += go;
			for (int u = T; u != S; u = PreN[u]) {
				C += go * E[PreE[u]].cost;
				E[PreE[u]].cap -= go;
				E[PreE[u] ^ 1].cap += go;
			}
		}
		return std::make_pair(F, C);
	}
}
	
bool Vis[MAXN * 3 + 5];

void Dfs1(int u) {
	Vis[u - N] = true;
	std::cout << S[u - N] << '\n';
	for (int i = Dinic::Adj[u]; ~i; i = Dinic::E[i].nxt) {
		int v = Dinic::E[i].v;
		if (v <= N && !Vis[v] && Dinic::E[i ^ 1].cap > 0) {
			Dfs1(v + N);
			break;
		}
	}
}

void Dfs2(int u) {
	Vis[u - N] = true;
	for (int i = Dinic::Adj[u]; ~i; i = Dinic::E[i].nxt) {
		int v = Dinic::E[i].v;
		if (v < N && !Vis[v] && Dinic::E[i ^ 1].cap > 0) {
			Dfs2(v + N);
			break;
		}
	}
	std::cout << S[u - N] << '\n';
}

int main() {
	scanf("%d%d", &N, &M);
	Dinic::Init(2 * N);
	for (int i = 1; i <= N; i++) {
		std::cin >> S[i];
		ID[S[i]] = i;
		Dinic::AddEdge(i, i + N, (i == 1 || i == N) ? 2 : 1, -1);
	}
	bool flag = false;
	for (int i = 1; i <= M; i++) {
		std::string a, b;
		std::cin >> a >> b;
		int u = ID[a], v = ID[b];
		if (u > v) std::swap(u, v);
		if (u == 1 && v == N) flag = true;
		Dinic::AddEdge(u + N, v, INF, 0);
	}
	std::pair<int, int> Res = Dinic::MinCostMaxFlow(1, N + N);
	if (Res.first != 2) {
		if (flag)
			std::cout << 2 << S[1] << '\n' << S[N] << '\n' << S[1] << '\n';
		else
			puts("No Solution!");
		return 0;
	}
	printf("%d\n", -Res.second - 2);
	Dfs1(1 + N);
	Dfs2(1 + N);
	return 0;
}

剩下的待填= =

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值