230722 做题记录 // 网络流 24 题 (3/24)

XSC062 / 2023-07-22 / 原文

知耻而后勇,物极必反。

下文称「因为结点流量需要控制,故拆点」的操作为「保留节目」。


A. 星际转移问题

http://222.180.160.110:1024/contest/3952/problem/1

如果就按照题目给的路线图,我们显然无法考虑到飞船到达的时刻。同时 \(n\)\(m\) 又很小,我们就知道了,「人不能两次踏进同一条河流」,1 时刻的站 \(p\) 和 2 时刻的站 \(p\) 也不能是同一个站 \(p\)

考虑用 \((p, t)\) 表示 \(t\) 时刻的站 \(p\),然后对于每条路线跑个暴力连边,容量全部为 \(H_i\)

怎么控制时间最小呢?二分一下就可以了…… 然后最大流判定是不是满流的即可。由于空间站容量为无穷大,甚至没有保留节目 保留节目不再是保留节目了呜呜呜

以及注意到对于同一站点,前面的时刻可以留下来等后面的时刻,我们将同一站的前一时刻和后一时刻全部连边,容量为 \(k\)。以及保留节目对源点拆点以控制流量为 \(k\)

经实验答案最大为 29,所以把二分上界设为 30 即可 理论上来说答案可能很大,比如你谷最后一组数据的答案就是 900 多,所以我掐指一算用天才般的算术技巧开了 \(10^4\)。真的,数数位天才就是我。

woc,这题居然没人做,果然我还是太强了

为什么都跑去做 T4 了,这个不是按难度顺序排列的吗

哦哦,好像不是,那(Na)没(Mei)事(Shi)了(Le)。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int lim = 1e4;
const int inf = 1e18;
const int maxm = 4e5 + 5;
const int maxn = 5e4 + 15;
struct _ {
	int v, w, n;
	_() {}
	_(int v1, int w1, int n1) {
		v = v1, w = w1, n = n1;
	}
};
struct __ {
	int c;
	std::vector<int> p;
};
_ u[maxm];
__ w[maxn];
int h[maxn];
int l, mid, r;
int gs, gt, tot = 1;
int n, m, k, s, mt, x, res, y;
int vis[maxn], now[maxn], dep[maxn];
inline int fun(int p, int t) {
	return (p - 1) * mt + t;
}
inline int min(int x, int y) {
	return x < y ? x : y;
}
bool BFS(int n) {
	std::fill(vis + 1, vis + n + 1, 0);
	std::fill(dep + 1, dep + n + 1, 0); 
	std::queue<int> q;
	dep[gs] = 1, vis[gs] = 1;
	q.push(gs), now[gs] = h[gs];
	while (!q.empty()) {
		int f = q.front();
		q.pop();
		for (int i = h[f]; i; i = u[i].n) {
			int v = u[i].v, w = u[i].w;
			if (vis[v] == 1 || w == 0)
				continue;
			vis[v] = 1, now[v] = h[v];
			dep[v] = dep[f] + 1, q.push(v);
			if (v == gt)
				return 1;
		}
	}
	return 0;
}
int findP(int x, int flow = inf) {
	if (x == gt)
		return flow;
	int rest = flow, i;
	for (i = now[x]; rest && i; i = u[i].n) {
		int v = u[i].v, w = u[i].w;
		now[x] = i;
		if (dep[v] != dep[x] + 1 || w == 0)
			continue;
		int t = findP(v, min(rest, w));
		if (t == 0)
			dep[v] = 0;
		rest -= t;
		u[i].w -= t, u[i ^ 1].w += t;
	}
	return flow - rest;
}
inline int Dinic(int n) {
	int res = 0;
	while (BFS(n)) {
		int t = findP(gs);
		while (t) {
			res += t;
			t = findP(gs);
		}
	}
	return res;
}
inline void add(int x, int y, int w) {
	u[++tot] = _(y, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int w) {
	add(x, y, w), add(y, x, 0);
	return;
}
inline void Init(void) {
	tot = 1;
	memset(h, 0, sizeof (h));
	return;
}
inline bool check(int x) {
	Init();
	mt = x, s = fun(n, mt) + 1;
	gs = s + 1, gt = s + 2;
	addf(gs, s, k);
	for (int i = 1; i <= mt; ++i) {
		addf(s, fun(n - 1, i), k);
		addf(fun(n, i), gt, k);
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j < mt; ++j)
			addf(fun(i, j), fun(i, j + 1), k);
	}
	for (int i = 1; i <= m; ++i) {
		int p = 0, x = 0, la = 0;
		while (++p <= mt) {
			if (la != 0) {
				addf(fun(la, p - 1),
					fun(w[i].p[x], p), w[i].c);
			}
			la = w[i].p[x];
			if (++x >= w[i].p.size())
				x = 0;
		}
	}
	return (Dinic(gt) == k);
}
int main() {
	read(n), read(m), read(k);
	for (int i = 1; i <= m; ++i) {
		read(w[i].c), read(y);
		while (y--) {
			read(x);
			if (x == 0)
				x = n + 1;
			else if (x == -1)
				x = n + 2;
			w[i].p.push_back(x);
		}
	}
	n += 2;
	l = 1, r = lim;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			res = mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	print(res ? res - 1 : 0, '\n');
	return 0;
}
} // namespace XSC062
#undef int

B. 最长递增子序列

http://222.180.160.110:1024/contest/3952/problem/2

就算知道不是按难度顺序排列我也要顺序开题。欸嘿,就是玩。

第一问很水,跑个 DP 就行。

第二问有点意思,取出就代表只能选一次,没想到这次保留节目这么前,总之把每个数拆成入点和出点,这样就可以只选一次了。

那怎么保证每次找到的流一定是 LIS 呢?其实这和我们 Dinic 的深度分层数组有异曲同工之妙,我们把 \(f_i = f_j + 1(i>j,A_i\ge A_j)\)\((j, i)\) 连边,容量为 \(1\) 即可。

然后源点只和满足 \(f_x = 1\)\(x\) 连边,相应地,汇点之和满足 \(f_x = \text{LIS}\)\(x\) 连边。

第三问很好想啊,我们把 \(1\) 到源点和 \(n\) 到汇点的容量设成无穷大就好。

然后踩了半天的坑,这道题的保留节目部分不知道为什么必须要连双向边。

#define int long long
namespace XSC062 {
using namespace fastIO;
const int inf = 1e18;
const int maxm = 4e5 + 5;
const int maxn = 5e5 + 15;
struct _ {
	int v, w, n;
	_() {}
	_(int v1, int w1, int n1) {
		v = v1, w = w1, n = n1;
	}
};
_ u[maxm];
int n, res;
int gs, gt, tot = 1;
int a[maxn], h[maxn], f[maxn];
int vis[maxn], now[maxn], dep[maxn];
inline int min(int x, int y) {
	return x < y ? x : y;
}
inline int max(int x, int y) {
	return x > y ? x : y;
}
bool BFS(int n) {
	std::fill(vis + 1, vis + n + 1, 0);
	std::fill(dep + 1, dep + n + 1, 0); 
	std::queue<int> q;
	dep[gs] = 1, vis[gs] = 1;
	q.push(gs), now[gs] = h[gs];
	while (!q.empty()) {
		int f = q.front();
		q.pop();
		for (int i = h[f]; i; i = u[i].n) {
			int v = u[i].v, w = u[i].w;
			if (vis[v] == 1 || w == 0)
				continue;
			vis[v] = 1, now[v] = h[v];
			dep[v] = dep[f] + 1, q.push(v);
			if (v == gt)
				return 1;
		}
	}
	return 0;
}
int findP(int x, int flow = inf) {
	if (x == gt)
		return flow;
	int rest = flow, i;
	for (i = now[x]; rest && i; i = u[i].n) {
		int v = u[i].v, w = u[i].w;
		now[x] = i;
		if (dep[v] != dep[x] + 1 || w == 0)
			continue;
		int t = findP(v, min(rest, w));
		if (t == 0)
			dep[v] = 0;
		rest -= t;
		u[i].w -= t, u[i ^ 1].w += t;
	}
	return flow - rest;
}
inline int Dinic(int n) {
	int res = 0;
	while (BFS(n)) {
		int t = findP(gs);
		while (t) {
			res += t;
			t = findP(gs);
		}
	}
	return res;
}
inline void add(int x, int y, int w) {
	u[++tot] = _(y, w, h[x]);
	h[x] = tot;
	return;
}
inline void addf(int x, int y, int w) {
	add(x, y, w), add(y, x, 0);
	return;
}
int main() {
	read(n);
	gs = 2 * n + 1, gt = gs + 1;
	for (int i = 1; i <= n; ++i) {
		read(a[i]);
		f[i] = 1;
		addf(i, i + n, 1);
		addf(i + n, i, 1);
		for (int j = 1; j < i; ++j) {
			if (a[j] <= a[i])
				f[i] = max(f[i], f[j] + 1);
		}
		res = max(res, f[i]);
		for (int j = 1; j < i; ++j) {
			if (a[j] <= a[i] &&
					f[i] == f[j] + 1)
				addf(j, i + n, 1);
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (f[i] == 1) 
			addf(gs, i, 1);
		if (f[i] == res) 
			addf(i + n, gt, 1);
	}
	print(res, '\n');
	print(Dinic(gt), '\n');
	tot = 1;
	memset(h, 0, sizeof (h));
	for (int i = 1; i <= n; ++i) {
		addf(i, i + n, 1);
		addf(i + n, i, 1);
		for (int j = 1; j < i; ++j) {
			if (a[j] <= a[i] &&
					f[i] == f[j] + 1)
				addf(j, i + n, 1);
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (f[i] == 1)  {
			if (i == 1)
				addf(gs, i, inf);
			else addf(gs, i, 1);
		}
		if (f[i] == res) {
			if (i == n)
				addf(i + n, gt, inf);
			else addf(i + n, gt, 1);
		}
	}
	print(Dinic(gt), '\n');
	return 0;
}
} // namespace XSC062
#undef int

C. 餐巾计划问题

http://222.180.160.110:1024/contest/3952/problem/3

这个有点简单啊。