目录

AtCoder Beginner Contest 004

A

输出 \(N \times 2\) 。

B

题意

输入一个 \(4 \times 4\) 的矩形,输出它旋转 \(180\) 度的结果。

题解

只讨论 \(N \times N\) 的正方形,正方形的置换一般是通过两种操作实现的:

以下是笛卡尔坐标系。

通常优先从置换群角度考虑会容易。

再否则,考虑构造朴素的全等三角形。

再否则,角度比较麻烦的的情况考虑计算几何,坐标 rotate 存在公式。

这里只举几个置换群例子。

置换 \(f\) ,上下的镜面翻转 h[i][j] = g[N + 1 - i][j] :

\[\begin{matrix} A & B \\ D & C \\ \end{matrix} \rightarrow \begin{matrix} D & C \\ A & B \\ \end{matrix} \]

置换 \(g\) ,左右的镜面翻转 h[i][j] = g[i][N + 1 - j] :

\[\begin{matrix} A & B \\ D & C \\ \end{matrix} \rightarrow \begin{matrix} B & A \\ C & D \\ \end{matrix} \]

置换 \(h\) ,沿 y = x 翻转 h[i][j] = g[j][i] :

\[\begin{matrix} A & B \\ D & C \\ \end{matrix} \rightarrow \begin{matrix} C & B \\ D & A \\ \end{matrix} \]

这 \(3\) 个置换复合后可以得到一些操作。

比如:\(h \circ g \circ V\) 可以实现顺时针旋转 \(90\) 度。其中 \(h \circ g\) 为 h[i][j] = g[N + 1 - j][i] 。
比如:\(h \circ f \circ V\) 可以实现顺时针旋转 \(180\) 度。其中 \(h \circ g\) 为 h[i][j] = g[N + 1 - i][N + 1 - j] 。

view
	const int N = 4;
	std::vector<std::vector<char> > g(N + 1, std::vector<char>(N + 1));
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			std::cin >> g[i][j];
		}
	}
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			std::cout << g[N + 1 - i][N + 1 - j] << " \n"[j == N];
		}
	}

C

题意

一开始有长度为 \(6\) 的序列 \(1, 2, 3, 4, 5, 6\) ,从 \(1\) 开始编号。执行 \(N\) 次操作 \(swap(a_{i \bmod 5 + 1}, a_{i \bmod 5 + 2})(i = 0, 1, 2, \cdots, N - 1)\) 。
询问 \(N\) 次操作后的序列。

\(0 \leq N \leq 10^{9}\)

题解

对于循环意义下的 \(swap\) ,每个循环是一个相等的置换。

模意义下的循环节 \(s\) 不超过模数 \(mod\) ,即 \(s \leq mod\) 。

可以找到 \(s = n - 1\) 。进一步观察到 \(swap(a_{i}, a_{(i + 1) \bmod n})(i = 0, 1, 2, \cdots, n - 2)\) 构成的置换是循环左移一位。不妨令这个置换为 \(f\) 。

根据 \(\forall f, \exists x, f^{x} \circ v = v\) 。一个简洁的观察是:

显然地 \(n(n - 1)\) 次操作后序列不变,我们只需要暴力 \(N \bmod (n(n - 1))\) 次操作即可。时间复杂度 \(O(n^{2})\) 。

	int a[] = {0, 1, 2, 3, 4, 5, 6};
	int N; std::cin >> N;
	N %= 30;
	for (int i = 0; i < N; i++) {
		std::swap(a[i % 5 + 1], a[i % 5 + 2]);
	}
	for (int i = 1; i <= 6; i++) {
		std::cout << a[i];
	}
	std::cout << "\n";

考虑更特殊的性质,此处由于 \(f\) 为循环移位,于是 \(f^{x}\) 可以被 \(O(1)\) 确定,\(O(n)\) 实现。

最后要剩下需要暴力的次数 \(< s\) ,是 \(T(s) = T(n) = O(n)\) 级别。

设 \(R = N \bmod (n(n - 1)) < n(n - 1)\) ,在当前条件下,有 \(R \sim N\) 。

实际上会进行 \(P = \lfloor \frac{R}{n - 1} \rfloor < n\) 次循环左移,可以 \(O(n)\) 直接处理。这个置换是:

\[\begin{pmatrix} a_0 & a_1 & a_2 & \cdots & a_{n - 1} \\ a_P & a_{P + 1} & a_{P + 2} & \cdots & a_{(P - 1) \bmod n} \\ \end{pmatrix} \]

\(0 \ base\) 下的实现:

	int b[n] = {0}; memcpy(b, a, sizeof b);
	for (int i = 0; i < n; i++) {
		a[i] = b[P % n];
		P++;
	}

再进行 \(Q = R \bmod (n - 1) < n = O(n)\) 次暴力。

\(0 \ base\) 下的实现为:

	for (int i = 0; i < Q; i++) {
		std::swap(a[i], a[i + 1]);
	}

\(1 \ base\) 下的总实现为:

	const int n = 6;
	int a[n + 1] = {0, 1, 2, 3, 4, 5, 6};
	int N; std::cin >> N;
	N %= n * (n - 1);
	int P = N / (n - 1), Q = N % (n - 1);

	int b[n + 1] = {0}; memcpy(b, a, sizeof b);
	P++;
	for (int i = 1; i <= n; i++) {
		a[i] = b[(P - 1) % n + 1];
		P++;
	}
	
	for (int i = 1; i <= Q; i++) {
		std::swap(a[i], a[i + 1]);
	}
	for (int i = 1; i <= n; i++) {
		std::cout << a[i];
	}
	std::cout << "\n";

D

diffculty 1861

题意

在一根无限长的整数数轴上,每个坐标放置着一个箱子。
\(-100\) 处放置了 \(R\) 个红球,\(0\) 处放置了 \(G\) 个绿球,\(100\) 处放置了 \(B\) 个蓝球。
每次操作可以将一个球移动到相邻箱子,每个箱子中不能存在同色球。
询问最少经过多少次操作可以使数轴上的所有箱子存在球的数量 \(\leq 1\) 。

题解

这是个极其经典的网络流和 DP 模型。

解法一:

裸的无脑最小费用最大流 \(MinCostMaxFlow\) ,时间复杂度 O(流量上限 * spfa 时间),最坏是 \(O(\sum Cost \times V^{2})\) 。

令 \(N = max(100 + R + G + B)\) 。
不难发现实际有用的一个非常宽松的上界是 \([-N, N]\) ,这个区域已经能放下所有球。球移动到这个范围之外只会浪费操作数,没有意义。

建立源点连向 \(-100, 0, 100\) 位置的三个点,边的 cap/cost 分别为 \((R, 0), (G, 0), (B, 0)\) 。三个点集为 \(V_1\) 。

建立 \([-N, N]\) 这 \(2N\) 个点连向汇点,边的 cap/cost 为 \((1, 0)\) 。这 \(2N\) 个点点集为 \(V_2\) 。

对 \(V_1\) 和 \(V_2\) 的点连边,边的 cap/cost 为 \((1, dist)\) 。费用为数轴上两点的距离。

于是就是裸的最小费用最大流。可以发现网络流其实非常无赖。

关于网络流,可以不会。

但是要有板子,最好知道大概怎么流的,知道一些关键点比如:流量平衡、残量网络,并且会 \(addedge\) 。
网络流 24 题要总结出一份板子出来,方便抄。

这个题只用板子一贴,然后 \(addedge\) 就行。

解法二:
令 \(N = max(100 + R + G + B)\) 。
不难发现实际有用的一个非常宽松的上界是 \([-N, N]\) ,这个区域已经能放下所有球。球移动到这个范围之外只会浪费操作数,没有意义。
由于每个位置有 \(4\) 种放置可能,无球、红球、绿球、蓝球。我们可以使用 dp 。

我们从 \(-N\) 到 \(N\) 顺序遍历,枚举当前位置的放置可能。

使用 dp[考虑到第几个位置][还剩下多少红球][还剩下多少绿球][还剩下多少蓝球] ,存储最小操作次数。

初始定义良序 dp[-N - 1][R][G][B] = 0 。(当然也可以定义所有 dp[i][R][G][B] = 0 ,或者直接转移出这些状态)

设 \(k = 0, 1, 2, 3,\) 分别对应空放、放红球、放绿球、放蓝球。

有 dp 公式:

\[\left \{ \begin{aligned} dp[i][r][g][b] &= min(dp[i][r][g][b],\ dp[i - 1][r][g][b]), &k = 0 \\ dp[i][r][g][b] &= min(dp[i][r][g][b],\ dp[i - 1][r + 1][g][b] + |-100 - i|), &k = 1,\ r + 1 \leq R,\ dp[i - 1][r + 1][g][b] < inf \\ dp[i][r][g][b] &= min(dp[i][r][g][b],\ dp[i - 1][r][g + 1][b] + |0 - i|), &k = 2,\ g + 1 \leq G,\ dp[i - 1][r][g + 1][b] < inf \\ dp[i][r][g][b] &= min(dp[i][r][g][b],\ dp[i - 1][r][g][b + 1] + |100 - i|), &k = 3,\ b + 1 \leq B,\ dp[i - 1][r][g][b + 1] < inf \\ \end{aligned} \right . \]

时空复杂度 \(O((R + G + B) R G B)\) 。
当 \(max(R, G, B) \leq 30\) 在空间上可以接受,当 \(max(R, G, B) \leq 100\) 在时间上可以接受。


	memset(dp, 127, sizeof dp);

	int z = N + 1;
	int pos[] = {0, z - 100, z, z + 100};
	dp[0][R][G][B] = 0;
	for (int i = 1; i <= 2 * N + 1; i++) {
		for (int k = 0; k <= 3; k++) {
			for (int r = 0; r <= R; r++) {
				for (int g = 0; g <= G; g++) {
					for (int b = 0; b <= B; b++) {
						if (k == 0) {
							chkmin(dp[i][r][g][b], dp[i - 1][r][g][b]);
						}
						else if (k == 1 && r + 1 <= R && dp[i - 1][r + 1][g][b] < inf) {
							chkmin(dp[i][r][g][b], dp[i - 1][r + 1][g][b] + abs(pos[k] - i));
						}
						else if (k == 2 && g + 1 <= G && dp[i - 1][r][g + 1][b] < inf) {
							chkmin(dp[i][r][g][b], dp[i - 1][r][g + 1][b] + abs(pos[k] - i));
						}
						else if (k == 3 && b + 1 <= B && dp[i - 1][r][g][b + 1] < inf) {
							chkmin(dp[i][r][g][b], dp[i - 1][r][g][b + 1] + abs(pos[k] - i));
						}
					}
				}
			}
		}
	}
	std::cout << dp[2 * N + 1][0][0][0] << "\n";
}

AC 37 个点,RE 48 个点。

因为第 \(i\) 维只由第 \(i - 1\) 维转移,空间可以优化到 \(O(RGB)\) 。

于是当 \(max(R, G, B) \leq 100\) 在时空上都可以接受。

const int inf = 1 << 30;
void chkmin(int &a, int b) { a = std::min(a, b); }
int dp[101][101][101], vdp[101][101][101];
void solve() {
	int R, G, B; std::cin >> R >> G >> B;
	assert(R <= 100 && G <= 100 && B <= 100);
	
	const int N = 400;
	memset(vdp, 127, sizeof vdp);
	memset(dp, 127, sizeof dp);

	int z = N + 1;
	int pos[] = {0, z - 100, z, z + 100};
	for (int i = 1; i <= 2 * N + 1; i++) {
		vdp[R][G][B] = 0;
		for (int k = 0; k <= 3; k++) {
			for (int r = 0; r <= R; r++) {
				for (int g = 0; g <= G; g++) {
					for (int b = 0; b <= B; b++) {
						if (k == 0) {
							chkmin(dp[r][g][b], vdp[r][g][b]);
						}
						else if (k == 1 && r + 1 <= R && vdp[r + 1][g][b] < inf) {
							chkmin(dp[r][g][b], vdp[r + 1][g][b] + abs(pos[k] - i));
						}
						else if (k == 2 && g + 1 <= G && vdp[r][g + 1][b] < inf) {
							chkmin(dp[r][g][b], vdp[r][g + 1][b] + abs(pos[k] - i));
						}
						else if (k == 3 && b + 1 <= B && dp[r][g][b + 1] < inf) {
							chkmin(dp[r][g][b], vdp[r][g][b + 1] + abs(pos[k] - i));
						}
					}
				}
			}
		}
		memcpy(vdp, dp, sizeof vdp);
		memset(dp, 127, sizeof dp);
	}
	std::cout << vdp[0][0][0] << "\n";
}

AC 57 个点,RE 28 个点。

似乎已经没有比较显然的 dp 时空优化方法了,怎么办呢?

我们需要继续观察。

我们可以发现数轴上的颜色分配一定是 红、绿、蓝 固定三段的。

证明很简单,比如一个红球在绿球右边,那么把这两个球交换位置的结果会更优。

如果位置已经固定,那么数轴上每个位置只有两种状态,放球、不放球。

现在问题已经于颜色无关,所以不需要维护三种颜色球剩余的数量,只需要维护总共的球剩余的数量。

同时可以直接计算出当前位置放置的哪种颜色的球,即可以直接计算转移的贡献。

计算 \(consume = R + G + B - rem\) ,若 \(consume \leq R\) 则是红球,再否则 \(consume \leq R + G\) 则是绿球,再否则是蓝球。

可以直接使用 if else 做到这一步。

一个更简洁、更快的定位目标点所在区间的写法:
定义左开右闭的区间数组 \(t = [-1, R, R + G, R + G + B]\) , lower_bound 左开右闭的区间。依据是 \(consume \leq t[i] \Rightarrow t[i] \geq consume\) 。
能查询到 \(consume\) 在 (-1, R], (R, R + G], (R + G, R + G + B] 哪个区间中。分别对应 \(1, 2, 3\) 。

有: dp[考虑到第几个位置][还剩多少球]

设 \(k = 0, 1\) 分别为不放球、放球。

良序初始化 dp[0][0] = 1 。枚举每个位置的状态。

有公式

\[\left \{ \begin{aligned} dp[i][k] &= min(dp[i][j], dp[i - 1][j]), &k = 0 \\ dp[i][k] &= min(dp[i][j], dp[i - 1][j + 1] + |-100 - i|) , &k = 1,\ R + G + B - j \leq R,\ dp[i - 1][j + 1] < inf \\ dp[i][k] &= min(dp[i][j], dp[i - 1][j + 1] + |0 - i|) , &k = 1,\ R + G + B - j \leq G,\ dp[i - 1][j + 1] < inf \\ dp[i][k] &= min(dp[i][j], dp[i - 1][j + 1] + |100 - i|) , &k = 1,\ R + G + B - j \leq B,\ dp[i - 1][j + 1] < inf \\ \end{aligned} \right . \]

时间复杂度 \(O((R + G + B)^{2})\) 。

由于第 \(i\) 维只由第 \(i - 1\) 维转移,可以通过滚动数组将空间复杂度优化到 \(O(R + G + B)\) 。

正解如下。

view
const int inf = 1 << 30;
void chkmin(int &a, int b) { a = std::min(a, b); }
void solve() {
	int R, G, B; std::cin >> R >> G >> B;
	
	const int N = 2000;
	std::vector<int> dp(R + G + B + 1, inf);
	std::vector<int> vdp(R + G + B + 1, inf);

	int z = N + 1;
	int pos[] = {0, z - 100, z, z + 100};
	
	vdp[R + G + B] = 0;
	for (int i = 1; i <= 2 * N + 1; i++) {
		for (int k = 0; k <= 1; k++) {
			for (int j = 0; j <= R + G + B; j++) {
				if (k == 0) {
					chkmin(dp[j], vdp[j]);
				}
				else if (k == 1 && j + 1 <= R + G + B) {
					int t[4] = {-1, R, R + G, R + G + B};
					int p = std::lower_bound(t + 1, t + 4, R + G + B - j) - t;
					assert(1 <= p && p <= 3);
					chkmin(dp[j], vdp[j + 1] + abs(pos[p] - i));
				}
			}
		}
		vdp = dp;
		dp.assign(R + G + B + 1, inf);
	}
	std::cout << vdp[0] << "\n";
}
posted @ 2024-07-15 21:36  03Goose  阅读(52)  评论(0)    收藏  举报