[NOIP2020] 移球游戏

按颜色分治挺显然,但题解有更简单的做法。

首先考虑 \(n = 2\) 的 情况,我们要让颜色为1的球全在第一根柱子上,手玩一下可以发现通解:假设第一根柱子有 \(t1\) 个 1,我们把第二根柱子前 \(t\) 个移到第三根柱子,再把第一根柱子上的 1 都移到第二根柱子,2 都移到第 3 根柱子。然后将第二根柱子上的 \(t1\) 个球和第三根柱子上的 \(m - t1\) 个球移动到 1 上,接着把第三根柱子上的球移动到第二根柱子上。这样我们相当于对第一根柱子完成了一次排序操作,此时第一根柱子一定是上面全为 2 而下面全为 1。显然,可以把这里的 2 移到第三根柱子上,再将第二根柱子拆开,如果是 1 就移到第一根柱子,是 2 就移到第三根柱子。这样,第一根上面全是 1, 第二根上面全是 2:

int t1 = 0;
for (int i = 1; i <= m; ++i) t1 += (stk[1][i] == 1);
for (int i = 1; i <= t1; ++i) move(2, 3);
while (top[1]) {
	if (stk[1][top[1]] == 1) move(1, 2);
	else move(1, 3);
}
for (int i = 1; i <= t1; ++i) move(2, 1);
for (int i = 1; i<= m - t1; ++i) move(3, 1);
while (top[3]) move(3, 2);
while (top[1] && stk[1][top[1]] == 2) move(1, 3);
while (top[2]) {
	if (stk[2][top[2]] == 1) move(2, 1);
	else move(2, 3);
}

现在可以推广到 \(n\) 任意的情况。我们可以只考虑如何填满第一根柱子,填满之后用同样的方法再填第二、第三根。我们假定第 \(i\) 根柱子上的颜色为 \(i\),并支持一种交换柱子的操作(容易发现这种操作并不会影响答案,只是方便理解实现)。

先令颜色不为 1 的球都变成 0,我们思考,要把 1 单独拿出来成一列就需要先把所有 1 都弄到每根柱子的顶部。直接这样构造全非常困难,因为我们能用的柱子很少,只有一个空列,但是假设有全 0 列就可以用它来干很多事情。我们钦定第 \(n\) 列全为 \(0\), 第 \(n + 1\) 列为空(具体实现的时候就通过交换柱子),考虑如何在只运用第 i 列,第 \(n\) 列的全 0 列和第 \(n + 1\) 的空列完成把第 \(i\) 列的 1 提到上面来的操作。

我们发现这和 \(n = 2\) 的情况很像,我们设第 \(i\) 列有 \(t1\) 个 1, 先从第 \(n\) 列移动 \(t1\) 个 0 到第 \(n + 1\) 列,然后把 第 \(i\) 列的 1 移到第 \(n\) 列,0 移到第 \(n + 1\) 列,这样我们发现第 \(n\) 列最上面全是 1 而下面全是 0,第 \(n + 1\) 列全 0,第 \(i\) 列为空,于是我们互换第 \(i\) 根柱子与第 \(n + 1\) 根柱子,再互换第 \(n\) 根柱子与第 \(i\) 根柱子,这样接完成了第 \(i\) 列的 1 提到上面来的操作。

inline void make1(int x, int p) {
	make0(x, p);
	for (int i = x; i <= n - 1; ++i) {
		int t1 = 0;
		for (int j = 1; j <= m; ++j) t1 += (stk[i][j] == p);
		for (int j = 1; j <= t1; ++j) move(n, n + 1);
		while (top[i]) {
			if (stk[i][top[i]] == p) move(i, n);
			else move(i, n + 1);
		} Swap(i, n + 1); Swap(i, n);
	} for (int i = x; i <= n - 1; ++i) {
		while (top[i] && stk[i][top[i]] == p) move(i, n + 1);
	} Swap(n + 1, x); Swap(n, n + 1);
	for (int i = x + 1; i <= n; ++i) {
		while (top[i] != m) move(n + 1, i);
	}
}

如上面代码所示,令 \(i = 1,2,3,...,n - 1\) 都操作完之后,把顶上的 1 都移到空列上,再交换空列与 1,用 1 把其它列顶上的空填满,这样就填满了第一列。以此类推,可以填满第 \(2,3\) 知道 \(n - 2\) 列。 最后只有三根柱子不够进行上面的操作,但我们发现这就相当于 \(n=2\) 的情况。最后我们考虑怎么构造全 0 列。

还是假设先在第 1 根柱子上(其它柱子也是一样的方法)构造全 0 列,然后通过交换柱子的方法换到第 \(n\) 列。还是和上面的解法相似,我们设 \(t1\) 为第 1 根柱子上 1 的个数,第根 \(n\) 柱子上前 \(t1\) 个球移到第 \(n + 1\) 根上,第一根柱子 中的 1 移到 第 \(n\) 根柱子,0 移到第 \(n + 1\) 根柱子,再把第 \(n+1\) 根柱子上的 0 移回去。最后把第二根柱子上的 0 移到 第一根,第二根里多出来的 0 和 1 移到第 \(n + 1\) 根(因为 1 的个数不大于 \(m\) ,所以前两根中 0 的个数不少于 \(m\),一定够放慢第一列)。这样第一列就是全 0 列了。再通过交换主子的操作把第 \(1\) 列换到第 \(n\),第二列空列换到第 \(n + 1\) 列。

inline void make0(int x, int p) {
	int t1 = 0;
	for (int i = 1; i <= m; ++i) t1 += (stk[x][i] == p);
	for (int i = 1; i <= t1; ++i) move(n, n + 1);
	while (top[x]) {
		if (stk[x][top[x]] == p) move(x, n);
		else move(x, n + 1);
	} for (int i = 1; i <= m - t1; ++i) move(n + 1, x);
	while (top[x + 1]) {
		if (stk[x + 1][top[x + 1]] == p || top[x] == m) move(x + 1, n + 1);
		else move(x + 1, x);
	} Swap(x, n); Swap(x + 1, n + 1);
}

完整代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cassert>

using namespace std;

const int N = 55, M = 405;

int stk[N][M], n, m, l[820005], r[820005], top[N], p[N], cnt = 0;

inline void move(int i, int j) {
//	assert(top[i] > 0); assert(top[j] < m);
	stk[j][++top[j]] = stk[i][top[i]--];
	l[++cnt] = p[i]; r[cnt] = p[j];
}

inline void Swap(int i, int j) {
	swap(stk[i], stk[j]);
	swap(top[i], top[j]);
	swap(p[i], p[j]);
}

inline void make0(int x, int p) {
	int t1 = 0;
	for (int i = 1; i <= m; ++i) t1 += (stk[x][i] == p);
	for (int i = 1; i <= t1; ++i) move(n, n + 1);
	while (top[x]) {
		if (stk[x][top[x]] == p) move(x, n);
		else move(x, n + 1);
	} for (int i = 1; i <= m - t1; ++i) move(n + 1, x);
	while (top[x + 1]) {
		if (stk[x + 1][top[x + 1]] == p || top[x] == m) move(x + 1, n + 1);
		else move(x + 1, x);
	} Swap(x, n); Swap(x + 1, n + 1);
}

inline void make1(int x, int p) {
	make0(x, p);
	for (int i = x; i <= n - 1; ++i) {
		int t1 = 0;
		for (int j = 1; j <= m; ++j) t1 += (stk[i][j] == p);
		for (int j = 1; j <= t1; ++j) move(n, n + 1);
		while (top[i]) {
			if (stk[i][top[i]] == p) move(i, n);
			else move(i, n + 1);
		} Swap(i, n + 1); Swap(i, n);
	} for (int i = x; i <= n - 1; ++i) {
		while (top[i] && stk[i][top[i]] == p) move(i, n + 1);
	} Swap(n + 1, x); Swap(n, n + 1);
	for (int i = x + 1; i <= n; ++i) {
		while (top[i] != m) move(n + 1, i);
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= m; ++j)
			scanf("%d", &stk[i][j]);
	for (int i = 1; i <= n; ++i) p[i] = i, top[i] = m; p[n + 1] = n + 1;
	if (n == 2) {
		int t1 = 0;
		for (int i = 1; i <= m; ++i) t1 += (stk[1][i] == 1);
		for (int i = 1; i <= t1; ++i) move(2, 3);
		while (top[1]) {
			if (stk[1][top[1]] == 1) move(1, 2);
			else move(1, 3);
		}
		for (int i = 1; i <= t1; ++i) move(2, 1);
		for (int i = 1; i<= m - t1; ++i) move(3, 1);
		while (top[3]) move(3, 2);
		while (top[1] && stk[1][top[1]] == 2) move(1, 3);
		while (top[2]) {
			if (stk[2][top[2]] == 1) move(2, 1);
			else move(2, 3);
		}
	} else {
		for (int i = 1; i < n - 1; ++i) make1(i, i);
		int t1 = 0;
		for (int i = 1; i <= m; ++i) t1 += (stk[n - 1][i] == n - 1);
		for (int i = 1; i <= t1; ++i) move(n, n + 1);
		while (top[n - 1]) {
			if (stk[n - 1][top[n - 1]] == n - 1) move(n - 1, n);
			else move(n - 1, n + 1);
		}
		for (int i = 1; i <= t1; ++i) move(n, n - 1);
		for (int i = 1; i<= m - t1; ++i) move(n + 1, n - 1);
		while (top[n + 1]) move(n + 1, n);
		while (top[n - 1] && stk[n - 1][top[n - 1]] == n) move(n - 1, n + 1);
		while (top[n]) {
			if (stk[n][top[n]] == n - 1) move(n, n - 1);
			else move(n, n + 1);
		}
	}
	printf("%d\n", cnt);
	for (int i = 1; i <= cnt; ++i) printf("%d %d\n", l[i], r[i]);
	return 0;
} 
posted @ 2021-11-12 17:03  Smallbasic  阅读(259)  评论(0)    收藏  举报