[NOIP2020] 移球游戏
按颜色分治挺显然,但题解有更简单的做法。
首先考虑 的 情况,我们要让颜色为1的球全在第一根柱子上,手玩一下可以发现通解:假设第一根柱子有 个 1,我们把第二根柱子前 个移到第三根柱子,再把第一根柱子上的 1 都移到第二根柱子,2 都移到第 3 根柱子。然后将第二根柱子上的 个球和第三根柱子上的 个球移动到 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);
}
现在可以推广到 任意的情况。我们可以只考虑如何填满第一根柱子,填满之后用同样的方法再填第二、第三根。我们假定第 根柱子上的颜色为 ,并支持一种交换柱子的操作(容易发现这种操作并不会影响答案,只是方便理解实现)。
先令颜色不为 1 的球都变成 0,我们思考,要把 1 单独拿出来成一列就需要先把所有 1 都弄到每根柱子的顶部。直接这样构造全非常困难,因为我们能用的柱子很少,只有一个空列,但是假设有全 0 列就可以用它来干很多事情。我们钦定第 列全为 , 第 列为空(具体实现的时候就通过交换柱子),考虑如何在只运用第 i 列,第 列的全 0 列和第 的空列完成把第 列的 1 提到上面来的操作。
我们发现这和 的情况很像,我们设第 列有 个 1, 先从第 列移动 个 0 到第 列,然后把 第 列的 1 移到第 列,0 移到第 列,这样我们发现第 列最上面全是 1 而下面全是 0,第 列全 0,第 列为空,于是我们互换第 根柱子与第 根柱子,再互换第 根柱子与第 根柱子,这样接完成了第 列的 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);
}
}
如上面代码所示,令 都操作完之后,把顶上的 1 都移到空列上,再交换空列与 1,用 1 把其它列顶上的空填满,这样就填满了第一列。以此类推,可以填满第 知道 列。 最后只有三根柱子不够进行上面的操作,但我们发现这就相当于 的情况。最后我们考虑怎么构造全 0 列。
还是假设先在第 1 根柱子上(其它柱子也是一样的方法)构造全 0 列,然后通过交换柱子的方法换到第 列。还是和上面的解法相似,我们设 为第 1 根柱子上 1 的个数,第根 柱子上前 个球移到第 根上,第一根柱子 中的 1 移到 第 根柱子,0 移到第 根柱子,再把第 根柱子上的 0 移回去。最后把第二根柱子上的 0 移到 第一根,第二根里多出来的 0 和 1 移到第 根(因为 1 的个数不大于 ,所以前两根中 0 的个数不少于 ,一定够放慢第一列)。这样第一列就是全 0 列了。再通过交换主子的操作把第 列换到第 ,第二列空列换到第 列。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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