网络流二十四题

既然学了网络流,这二十四题看来是非做不可了。暑假应该是没时间了,开学之后补上吧。


第一题 P1251 餐巾计划问题

分析 要求保证每天的干净餐巾够用,相当于这天开始时给出 \(r\) 条干净餐巾,并在结束时收到 \(r\) 条脏餐巾。那么可以把每一天的早上向汇点\((r,0)\) 的边,并从源点向每一天晚上连 \((r,0)\) 的边,这样由于最大流的限制,保证了一定会流满,现在来考虑其他操作。

每天可以把脏餐巾留到下一天,等价于今天晚上向明天晚上连 \((+\infty,0)\) 的边(注意不是早上因为早上不接受脏餐巾)。对于慢洗店和快洗店,可以从该天晚上向洗好的那天早上连 \((+\infty,s/f)\) 的边。这样就算是建模完成了,跑一遍费用流就行了。

本题似乎还有一种三分的做法,不过毕竟是训练网络流的题目,所以还是老老实实写费用流吧(

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

typedef long long ll;
const int maxn = 2005;
const ll INF = 0x3f3f3f3f3f3f3f3f;

int n, s, t, tot = 1;
int p, fco, sco, ftm, stm;
int first[maxn * 2], cur[maxn * 2];
ll dis[maxn * 2], maxFlow, minCost;
bool vis[maxn * 2];
struct Edge {
	int to, next; ll w, cost;
} e[maxn * 12];
std::queue<int> q;

inline void Add(int x, int y, ll w, ll cost) {
	e[++tot] = { y, first[x], w, cost };
	first[x] = tot;
}

bool SPFA() {
	memset(dis, INF, sizeof dis);
	
	q.push(s), dis[s] = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			if(dis[v] > dis[u] + e[i].cost && e[i].w)
				dis[v] = dis[u] + e[i].cost, q.push(v);
		}
	}
	
	return dis[t] < INF;
}

ll DFS(int u, ll flow) {
	if(u == t) return flow;
	
	vis[u] = 1;
	
	ll res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(dis[v] != dis[u] + e[i].cost || vis[v] || !e[i].w) continue;
		ll d = DFS(v, std::min(flow, e[i].w));
		
		e[i].w -= d, e[i ^ 1].w += d;
		flow -= d, res += d;
		minCost += d * e[i].cost;
		
		if(!flow) break;
	}
	
	if(flow) dis[u] = -1;
	return vis[u] = 0, res;
}

ll Dinic() {
	ll res = 0, x;
	while(SPFA()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) res += x;
	}
	return res;
}

int main() {
	scanf("%d", &n);
	
	s = 0, t = n * 2 + 1;
	for(int i = 1, r; i <= n; ++i) {
		scanf("%d", &r);
		
		Add(s, i * 2, r, 0);
		Add(i * 2, s, 0, 0);
		Add(i * 2 - 1, t, r, 0);
		Add(t, i * 2 - 1, 0, 0);
		if(i < n) {
			Add(i * 2, i * 2 + 2, INF, 0);
			Add(i * 2 + 2, i * 2, 0, 0);
		}
	}
	
	scanf("%d%d%d%d%d", &p, &ftm, &fco, &stm, &sco);
	for(int i = 1; i <= n; ++i) {
		Add(s, i * 2 - 1, INF, p);
		Add(i * 2 - 1, s, 0, -p);
		if(i + ftm <= n) {
			Add(i * 2, (i + ftm) * 2 - 1, INF, fco);
			Add((i + ftm) * 2 - 1, i * 2, 0, -fco);
		}
		if(i + stm <= n) {
			Add(i * 2, (i + stm) * 2 - 1, INF, sco);
			Add((i + stm) * 2 - 1, i * 2, 0, -sco);
		}
	}
	
	maxFlow = Dinic();
	
	printf("%lld", minCost);
}

第二题 P2754 [CTSC1999]家园 / 星际转移问题

分析 并查集判断连通性,然后依次枚举每一个时间,按照时间建出对应节点,连边跑最大流,找到第一个大于等于需求的时间输出即可。可以直接重复利用残量网络。

#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>

const int maxm = 55;
const int maxn = 20;
const int Maxn = maxn * 5000;
const int Maxm = maxm * 10000;
const int INF = 0x3f3f3f3f;

int n, m, k, s, t, tot = 1;
int maxFlow, fa[maxn], h[maxm];
int dis[Maxn];
int first[Maxn], cur[Maxn];
struct Edge {
	int to, next, w;
} e[Maxm * 2];

std::queue<int> q;
std::vector<int> to[maxm];

inline int GetFather(int x) { return fa[x] == x ? x : fa[x] = GetFather(fa[x]); }
inline void Add(int x, int y, int w) {
	e[++tot] = { y, first[x], w };
	first[x] = tot;
}
inline void Addedge(int x, int y, int w) { Add(x, y, w), Add(y, x, 0); }

bool BFS() {
	memset(dis, INF, sizeof dis);
	
	q.push(s), dis[s] = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			
			if(dis[v] < INF || !e[i].w) continue;
			dis[v] = dis[u] + 1, q.push(v);
		}
	}
	return dis[t] < INF;
}

int DFS(int u, int flow) {
	if(u == t) return flow;
	
	int res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(!e[i].w || dis[v] != dis[u] + 1) continue;
		
		int f = DFS(v, std::min(flow, e[i].w));
		
		res += f, flow -= f;
		e[i].w -= f, e[i ^ 1].w += f;
		
		if(!flow) break;
	}
	
	if(flow) dis[u] = INF;
	
	return res;
}

void Dinic(int x = 0) {
	while(BFS()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) maxFlow += x;
	}
}

int main() {
	scanf("%d%d%d", &n, &m, &k), ++n;
	for(int i = 0; i <= n; ++i) fa[i] = i;
	for(int i = 1, r; i <= m; ++i) {
		scanf("%d%d", &h[i], &r);
		for(int j = 0, x; j < r; ++j)
			scanf("%d", &x), to[i].push_back(x + 1);
		for(auto u : to[i]) {
			for(auto v : to[i]) {
				int fau = GetFather(u), fav = GetFather(v);
				if(fau != fav) fa[fau] = fav;
			}
		}
	}
	
	if(GetFather(0) != GetFather(1)) return puts("0"), 0;
	
	s = 1, t = 0;
	for(int tim = 1; ; ++tim) {
		Addedge(tim * (n + 1), (tim - 1) * (n + 1), INF);
		for(int i = 1; i <= n; ++i)
			Addedge(i + (tim - 1) * (n + 1), i + tim * (n + 1), INF);
		
		for(int i = 1; i <= m; ++i) {
			int last = (tim - 1) % to[i].size(), now = tim % to[i].size();
			Addedge(to[i][last] + (tim - 1) * (n + 1), to[i][now] + tim * (n + 1), h[i]);
		}
		
		Dinic();
		
		if(maxFlow >= k) return printf("%d", tim), 0;
	}
}

第三题 P2756 飞行员配对方案问题

分析 大力二分图匹配即可。

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

const int maxn = 105;
const int INF = 0x3f3f3f3f;

int n, m, s, t, tot = 1;
int first[maxn], cur[maxn];
int maxFlow, dis[maxn];
std::queue<int> q;
struct Edge {
	int to, next, w; bool neg;
} e[maxn * maxn];

inline void Add(int u, int v, int w, bool b) {
	e[++tot] = { v, first[u], w, b };
	first[u] = tot;
}
inline void Adde(int u, int v, int w) { Add(u, v, w, 0), Add(v, u, 0, 1); }

bool BFS() {
	memset(dis, INF, sizeof dis);
	
	q.push(s), dis[s] = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			
			if(dis[v] < INF || !e[i].w) continue;
			dis[v] = dis[u] + 1, q.push(v);
		}
	}
	return dis[t] < INF;
}

int DFS(int u, int flow) {
	if(u == t) return flow;
	
	int res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(!e[i].w || dis[v] != dis[u] + 1) continue;
		
		int f = DFS(v, std::min(flow, e[i].w));
		e[i].w -= f, e[i ^ 1].w += f;
		res += f, flow -= f;
		
		if(!flow) break;
	}
	if(flow) dis[u] = INF;
	
	return res;
}

void Dinic(int x = 0) {
	while(BFS()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) maxFlow += x;
	}
}

int main() {
	scanf("%d%d", &m, &n), s = 0, t = n + 1;
	for(int i = 1; i <= m; ++i) Adde(s, i, 1);
	for(int i = m + 1; i <= n; ++i) Adde(i, t, 1);
	
	int u, v;
	while(scanf("%d%d", &u, &v) && u >= 0 && v >= 0) Adde(u, v, INF);
	
	Dinic(), printf("%d\n", maxFlow);

    for(int u = m + 1; u <= n; ++u)
        for(int i = first[u]; i; i = e[i].next)
        	if(e[i].neg && e[i].w) { printf("%d %d\n", e[i].to, u); break; }
}

第四题 P2761 软件补丁问题

分析 虽然说是网络流但是和网络流没什么关系。大力建图跑最短路即可。复杂度玄学。

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

#define fi first
#define se second

typedef std::pair<int, int> pii;
const int maxn = 21;
const int maxm = 105;
const int N = 1 << maxn;
const int INF = 0x3f3f3f3f;

int n, m, tot;
int b1[maxm], b2[maxm], f1[maxm], f2[maxm];
int w[maxm], dis[N];
std::priority_queue<pii, std::vector<pii>, std::greater<pii> > q;

int main() {
	memset(dis, 0x3f, sizeof dis);
	
	scanf("%d%d", &n, &m);
	const int M = 1 << n;
	for(int i = 1; i <= m; ++i) {
		scanf("%d", &w[i]);
		
		char ch = getchar();
		for(int j = 1; j <= n; ++j) {
			ch = getchar();
			b1[i] = (b1[i] << 1) + (ch == '+');
			b2[i] = (b2[i] << 1) + (ch == '-');
		}
		getchar();
		for(int j = 1; j <= n; ++j) {
			ch = getchar();
			f1[i] = (f1[i] << 1) + (ch == '-');
			f2[i] = (f2[i] << 1) + (ch == '+');
		}
	}
	
	dis[M - 1] = 0, q.emplace(0, M - 1);
	while(!q.empty()) {
		pii T = q.top(); q.pop();
		
		if(dis[T.se] != T.fi) continue;
		for(int i = 1; i <= m; ++i) {
			if((T.se & b1[i]) != b1[i] || (T.se & b2[i]) != 0)
				continue;
			
			int v = T.se & ~f1[i] | f2[i];
			if(dis[v] > dis[T.se] + w[i]) {
				dis[v] = dis[T.se] + w[i];
				q.emplace(dis[v], v);
			}
		}
	}
	
	printf("%d", dis[0] < INF ? dis[0] : 0);
}

第五题 P2762 太空飞行计划问题

分析 考虑最小割模型,从源点向每一个实验连边,从每一个实验向对应的仪器连边,从每一个仪器向汇点连边,跑最小割即可。

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

const int maxn = 105;
const int INF = 0x3f3f3f3f;

int n, m, s, t, tot = 1, sum;
int maxFlow, first[maxn];
int cur[maxn], dis[maxn];
int tool[maxn][maxn];
int ans[maxn]; bool isans[maxn];
struct Edge {
	int to, next, w;
} e[maxn * maxn];

inline void Add(int u, int v, int w) {
	e[++tot] = { v, first[u], w };
	first[u] = tot;
}
inline void Adde(int u, int v, int w) { Add(u, v, w), Add(v, u, 0); }
inline int abs(int x) { return x < 0 ? -x : x; }

int getint(int x = 0) {
	char ch = getchar();
	while(ch < '0' || ch > '9')	ch = getchar();
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - 48, ch = getchar();
	return ch == '\r' || ch == '\n' ? -x : x;
}

std::queue<int> q;
bool BFS() {
	memset(dis, INF, sizeof dis);
	
	q.push(s), dis[s] = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			if(dis[v] < INF || !e[i].w) continue;
			dis[v] = dis[u] + 1, q.push(v);
		}
	}
	return dis[t] < INF;
}

int DFS(int u, int flow) {
	if(u == t) return flow;
	
	int res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(dis[v] != dis[u] + 1 || !e[i].w) continue;
		int f = DFS(v, std::min(flow, e[i].w));
		
		res += f, flow -= f;
		e[i].w -= f, e[i ^ 1].w += f;
		
		if(!flow) break;
	}
	if(flow) dis[u] = INF;
	
	return res;
}

void Dinic(int x = 0) {
	while(BFS()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) maxFlow += x;
	}
}

void check() {
	memset(dis, INF, sizeof dis);
	
	q.push(s), dis[s] = 0;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		
		if(u > s && u < m + 1) ans[++ans[0]] = u;
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			if(dis[v] < INF || !e[i].w) continue;
			dis[v] = dis[u] + 1, q.push(v);
		}
	}
	
	std::sort(ans + 1, ans + ans[0] + 1);
	for(int i = 1; i <= ans[0]; ++i) {
		printf("%d", ans[i]);
		putchar(i < ans[0] ? ' ' : '\n');
		for(int j = 1; j <= tool[ans[i]][0]; ++j)
			isans[tool[ans[i]][j]] = 1;
	}
	bool flag = 0;
	for(int i = 1; i <= n; ++i) {
		if(isans[i]) {
			if(flag) putchar(' ');
			flag = 1, printf("%d", i);
		}
	}
}

int main() {
	m = getint(), n = -getint();
	
	s = 0, t = n + m + 1;
	for(int i = 1, p, x; i <= m; ++i) {
		Adde(s, i, p = getint()), sum += p;
		while(x = getint()) {
			Adde(i, abs(x) + m, INF);
			tool[i][++tool[i][0]] = abs(x);
			if(x < 0) break;
		}
	}
	for(int i = m + 1, p; i <= m + n; ++i) Adde(i, t, abs(getint()));
	Dinic(), check(), printf("\n%d", sum - maxFlow);
}

第六题 P2763 试题库问题

分析 直接大力二分图匹配即可,最后找方案只要判一下那些边没有容量了即可。

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>

const int maxn = 1005;
const int maxk = 25;
const int INF = 0x3f3f3f3f;

int n, k, s, t, m, tot = 1;
int dis[maxn + maxk];
int cur[maxn + maxk], first[maxn + maxk];
std::vector<int> ans[maxk];
struct Edge {
	int to, next, f;
} e[maxn * maxk];

inline void Add(int u, int v, int w) {
	e[++tot] = { v, first[u], w };
	first[u] = tot;
}
inline void Adde(int u, int v, int w) { Add(u, v, w), Add(v, u, 0); }

std::queue<int> q;
bool BFS() {
	memset(dis, INF, sizeof dis);
	
	dis[s] = 0, q.push(s);
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			if(!e[i].f) continue;
			
			int v = e[i].to;
			if(dis[v] > dis[u] + 1)
				dis[v] = dis[u] + 1, q.push(v);
		}
	} return dis[t] < INF;
}

int DFS(int u, int flow) {
	if(u == t) return flow;
	
	int res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(dis[v] != dis[u] + 1 || !e[i].f) continue;
		
		int f = DFS(v, std::min(flow, e[i].f));
		res += f, flow -= f, e[i].f -= f, e[i ^ 1].f += f;
		
		if(!flow) break;
	} if(flow) dis[u] = INF; return res;
}

int Dinic(int res = 0, int x = 0) {
	while(BFS()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) res += x;
	} return res;
}

int main() {
	scanf("%d%d", &k, &n), s = 0, t = n + k + 1;
	for(int i = 1, x; i <= k; ++i)
		scanf("%d", &x), Adde(i + n, t, x), m += x;
	for(int i = 1, p, x; i <= n; ++i) {
		Adde(s, i, 1), scanf("%d", &p);
		while(p --> 0) scanf("%d", &x), Adde(i, x + n, 1);
	}
	
	int maxF = Dinic();
	
	if(maxF != m) return puts("No Solution!"), 0;
	
	for(int u = 1; u <= n; ++u)
		for(int i = first[u]; i; i = e[i].next)
			if(!e[i].f && e[i].to) ans[e[i].to - n].push_back(u);
	
	for(int i = 1; i <= k; ++i) {
		printf("%d: ", i);
		for(int v : ans[i]) printf("%d ", v);
		putchar('\n');
	}
}

第七题 P2764 最小路径覆盖问题

分析 假定最开始每一条路径都是一个点,然后把每个点拆成入点和出点,每条边都从入点连向出点。考虑一下一个二分图匹配的意义:每连一条边相当于把两条路径 merge 在了一起,所以最后答案就是 \(n\) 减去最大流。跑二分图匹配即可。

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

const int maxn = 155;
const int maxm = 6005;
const int INF = 0x3f3f3f3f;

int n, m, s, t, tot = 1, to[maxn], deg[maxn];
int first[maxn * 2], cur[maxn * 2], dis[maxn * 2];
struct Edge {
	int to, next, f;
} e[(maxm + maxn) * 2];

inline void Add(int u, int v, int w) {
	e[++tot] = { v, first[u], w };
	first[u] = tot;
}
inline void Adde(int u, int v, int w) { Add(u, v, w), Add(v, u, 0); }

std::queue<int> q;
bool BFS() {
	memset(dis, INF, sizeof dis);
	
	dis[s] = 0, q.push(s);
	while(!q.empty()) {
		int u = q.front(); q.pop();
		for(int i = first[u]; i; i = e[i].next) {
			if(!e[i].f) continue;
			
			int v = e[i].to;
			if(dis[v] > dis[u] + 1)
				dis[v] = dis[u] + 1, q.push(v);
		}
	} return dis[t] < INF;
}

int DFS(int u, int flow) {
	if(u == t) return flow;
	
	int res = 0;
	for(int &i = cur[u]; i; i = e[i].next) {
		int v = e[i].to;
		
		if(dis[v] != dis[u] + 1 || !e[i].f) continue;
		
		int f = DFS(v, std::min(flow, e[i].f));
		res += f, flow -= f, e[i].f -= f, e[i ^ 1].f += f;
		
		if(!flow) break;
	} if(flow) dis[u] = INF; return res;
}

int Dinic(int res = 0, int x = 0) {
	while(BFS()) {
		memcpy(cur, first, sizeof cur);
		while(x = DFS(s, INF)) res += x;
	} return res;
}

void ANS(int u) {
	printf("%d ", u);
	if(to[u]) ANS(to[u]);
}

int main() {
	scanf("%d%d", &n, &m), s = 0, t = 2 * n + 1;
	for(int i = 1, u, v; i <= m; ++i)
		scanf("%d%d", &u, &v), Adde(u * 2 - 1, v * 2, 1);
	for(int i = 1; i <= n; ++i) Adde(s, i * 2 - 1, 1), Adde(i * 2, t, 1);
	
	int maxF = Dinic();
	
	for(int u = 1; u <= n; ++u)
		for(int i = first[u * 2 - 1]; i; i = e[i].next)
			if(!e[i].f) { to[u] = e[i].to / 2, ++deg[to[u]]; break; }
	
	for(int u = 1; u <= n; ++u)
		if(!deg[u]) ANS(u), putchar('\n');
	printf("%d", n - maxF);
}

第八题 P2765 魔术球问题
第九题 P2766 最长不下降子序列问题
第十题 P2770 航空路线问题
第十一题 P2774 方格取数问题
第十二题 P2775 机器人路径规划问题
第十三题 P3254 圆桌问题
第十四题 P3355 骑士共存问题
第十五题 P3356 火星探险问题
第十六题 P3357 最长k可重线段集问题
第十七题 P3358 最长k可重区间集问题
第十八题 P4009 汽车加油行驶问题

分析 考虑到 \(k\) 很小,于是我们可以建出分层图,然后按照题意连边。注意到程序中我们的流量始终为 1,于是退化成最短路问题。

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

#define fi first
#define se second
#define mp(x, y) std::make_pair(x, y)

const int maxn = 105;
const int maxV = maxn * maxn * 15;

const int dx[4] = { 0, 0, 1, -1 };
const int dy[4] = { 1, -1, 0, 0 };

int n, k, a, b, c, tot, ans = 1E+9;
int first[maxV], cost[4], dis[maxV];
bool g[maxn][maxn];
struct Edge {
	int to, next, w;
} e[maxV * 15];

inline void Add(int x, int y, int w) {
	e[++tot] = { y, first[x], w };
	first[x] = tot;
}

inline int getnum(int x, int y, int k) {
	return k * n * n + (x - 1) * n + y;
}

std::priority_queue<std::pair<int, int> > q;
void dij(int s) {
	memset(dis, 0x3f, sizeof dis);
	
	dis[s] = 0, q.push(mp(0, s));
	while(!q.empty()) {
		auto o = q.top(); q.pop();
		if(dis[o.se] != -o.fi) continue;
		
		int u = o.se;
		for(int i = first[u]; i; i = e[i].next) {
			int v = e[i].to;
			if(dis[v] > dis[u] + e[i].w) {
				dis[v] = dis[u] + e[i].w;
				q.push(mp(-dis[v], v));
			}
		}
	}
}

int main() {
	scanf("%d%d%d%d%d", &n, &k, &a, &b, &c);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			scanf("%d", &g[i][j]);
	
	cost[1] = cost[3] = b;
	for(int i = 1; i <= n; ++i) {
		for(int j = 1; j <= n; ++j) {
			for(int k = 0; k <= ::k; ++k) {
				if(k) for(int d = 0; d < 4; ++d) {
					int nx = i + dx[d], ny = j + dy[d];
					if(nx >= 1 && nx <= n && ny >= 1 && ny <= n)
						if(!g[nx][ny]) Add(getnum(i, j, k), getnum(nx, ny, k - 1), cost[d]);
						else Add(getnum(i, j, k), getnum(nx, ny, ::k), a + cost[d]);
				} Add(getnum(i, j, k), getnum(i, j, ::k), c + a);
			}
		}
	}
	
	dij(getnum(1, 1, k));
	for(int k = 0; k <= ::k; ++k)
		ans = std::min(ans, dis[getnum(n, n, k)]);
	printf("%d", ans);
}

第十九题 P4011 孤岛营救问题
第二十题 P4012 深海机器人问题
第二十一题 P4013 数字梯形问题
第二十二题 P4014 分配问题
第二十三题 P4015 运输问题
第二十四题 P4016 负载平衡问题

(题目来自洛谷)

posted @ 2020-08-24 00:03  whx1003  阅读(123)  评论(0编辑  收藏  举报