[NOIP2020] 移球游戏

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

首先考虑 n=2 的 情况,我们要让颜色为1的球全在第一根柱子上,手玩一下可以发现通解:假设第一根柱子有 t1 个 1,我们把第二根柱子前 t 个移到第三根柱子,再把第一根柱子上的 1 都移到第二根柱子,2 都移到第 3 根柱子。然后将第二根柱子上的 t1 个球和第三根柱子上的 mt1 个球移动到 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,...,n1 都操作完之后,把顶上的 1 都移到空列上,再交换空列与 1,用 1 把其它列顶上的空填满,这样就填满了第一列。以此类推,可以填满第 2,3 知道 n2 列。 最后只有三根柱子不够进行上面的操作,但我们发现这就相当于 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 @   Smallbasic  阅读(236)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示