题目
题目链接:https://www.luogu.com.cn/problem/P7115
小 C 正在玩一个移球游戏,他面前有 (n + 1) 根柱子,柱子从 (1 sim n + 1) 编号,其中 (1) 号柱子、(2) 号柱子、……、(n) 号柱子上各有 (m) 个球,它们自底向上放置在柱子上,(n + 1) 号柱子上初始时没有球。这 (n imes m) 个球共有 (n) 种颜色,每种颜色的球各 (m) 个。
初始时一根柱子上的球可能是五颜六色的,而小 C 的任务是将所有同种颜色的球移到同一根柱子上,这是唯一的目标,而每种颜色的球最后放置在哪根柱子则没有限制。
小 C 可以通过若干次操作完成这个目标,一次操作能将一个球从一根柱子移到另一根柱子上。更具体地,将 (x) 号柱子上的球移动到 (y) 号柱子上的要求为:
- (x) 号柱子上至少有一个球;
- (y) 号柱子上至多有 (m - 1) 个球;
- 只能将 (x) 号柱子最上方的球移到 (y) 号柱子的最上方。
小 C 的目标并不难完成,因此他决定给自己加加难度:在完成目标的基础上,使用的操作次数不能超过 (820000)。换句话说,小 C 需要使用至多 (820000) 次操作完成目标。
小 C 被难住了,但他相信难不倒你,请你给出一个操作方案完成小 C 的目标。合法的方案可能有多种,你只需要给出任意一种,题目保证一定存在一个合法方案。
思路
假设我们已经处理好了 (1sim i-1) 的小球,现在要把颜色为 (i) 的小球放在第 (i) 个柱子里。设颜色 (i) 的球为 (1),其余颜色的球为 (0)。
先把第 (i) 个柱子内的球全部变为 (0)。具体的,我们先把第 (i+1) 个柱子内的所有球全部移出去,如果柱子 (i) 内有 (k) 个 (1) 球,那么我们就把柱子 (i+2) 的前 (k) 个球放到第 (i+1) 个柱子内。然后依次弹出柱子 (i) 的小球,如果顶端的球是 (1),那么球放到柱子 (i+2) 上,否则放在柱子 (i+1) 上。
这样我们就把柱子 (i) 清空,并且把原来里面的球是 (1) 的放到了柱子 (i+2) 上,其余放到了 (i+1) 上。
接下来把 (i+1) 顶部的所有 (0) 球放到柱子 (i) 上,然后对柱子 (i+3) 进行操作:
- 如果柱子 (i+3) 顶部是 (0) 球并且柱子 (i) 没满,那么就把柱子 (i+3) 顶部的球放到 (i) 上。
- 否则把柱子 (i+3) 的球放到 (i+1) 上。
那么此时柱子 (i) 里面就全是 (0) 球了,而且因为 (1) 球总数只有 (m) 个,所以不用担心柱子会超过 (m) 个球。
接下来重复此操作:
- 枚举 (j=i+1 o n),将 (j) 柱子变空。由于之前操作完成后一定有一个柱子是空的,所以我们没有必要把球一个一个弹出,直接设 (id[x]) 表示 (x) 位置的柱子实际上是哪一个,然后交换编号即可。
- 此时 (i) 柱子是全 (0) 的,(j) 柱子是空的,设柱子 (j+1) 有 (k) 个 (1) 球,那么我们把柱子 (i) 的顶部 (k) 个球扔到柱子 (j) 中,然后用类似的方法,将柱子 (j+1) 中的 (1) 球扔进 (i) 的顶端,(0) 球扔进 (j) 的顶端。
- 经过上面两补后,我们发现 (i) 柱子顶端是若干个 (1) 球,(j) 柱子变为了全 (0),(j+1) 柱子变空了!
- 那么交换柱子之间的编号,使得 (i) 柱子全 (0),(j+1) 柱子为空,继续循环。
结束重复后,我们发现所有 (1) 球到了柱子 (1sim n) 的顶端,柱子 (n+1) 为空,那么我们就直接把所有柱子顶端的 (1) 球扔到柱子 (n+1) 中即可。
这样我们就成功的用 (4) 根柱子之间的转换,让 (1) 球全部到了柱子 (n+1)。接下来交换 (id[n+1]) 和 (id[i]),继续做颜色为 (i+1) 的球即可。
需要注意的是,当我们剩余柱子不够 (4) 个时,我们无法再这样操作。此时只剩下 (3) 个柱子和两种颜色的球没有分类,那么随便搞一下就好了,具体方法很多,不再赘述。
关于操作次数,对于第 (i) 个球,操作次数最多为 (6m+(n-i)m),再加上最后只剩两种球的时候的操作次数,总操作次数为 (6nm+frac{(n+1)(n-2)}{2}m+8m=612800)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=55,M=410,MAXN=820010;
int n,m,tot,id[N],cnt[N],col[N][M],ans[MAXN][2];
void move(int x,int y)
{
if (x==y) return;
ans[++tot][0]=x; ans[tot][1]=y;
col[y][++cnt[y]]=col[x][cnt[x]];
col[x][cnt[x]]=0; cnt[x]--;
}
int main()
{
freopen("data.txt","r",stdin);
freopen("ans.txt","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
cnt[i]=m; id[i]=i;
for (int j=1;j<=m;j++)
scanf("%d",&col[i][j]);
}
id[n+1]=n+1;
for (int i=1;i<=n-2;i++)
{
for (int j=1;j<=n+1;j++)
while (j!=i+1 && cnt[id[j]]<m && cnt[id[i+1]]) move(id[i+1],id[j]);
for (int j=cnt[id[i]];j>=1;j--)
if (col[id[i]][j]==i) move(id[i+2],id[i+1]);
for (int j=cnt[id[i]];j>=1;j--)
if (col[id[i]][j]==i) move(id[i],id[i+2]);
else move(id[i],id[i+1]);
for (int j=cnt[id[i+1]];j>=1 && col[id[i+1]][j]!=i;j--)
move(id[i+1],id[i]);
for (int j=cnt[id[i+3]];j>=1;j--)
if (col[id[i+3]][j]==i || cnt[id[i]]==m) move(id[i+3],id[i+1]);
else move(id[i+3],id[i]);
for (int j=i+1;j<=n;j++)
{
for (int k=1;k<=n+1;k++)
if (!cnt[id[k]])
{
swap(id[k],id[j]);
break;
}
for (int k=cnt[id[j+1]];k>=1;k--)
if (col[id[j+1]][k]==i) move(id[i],id[j]);
for (int k=cnt[id[j+1]];k>=1;k--)
if (col[id[j+1]][k]==i) move(id[j+1],id[i]);
else move(id[j+1],id[j]);
swap(id[i],id[j]);
}
for (int j=i+1;j<=n+1;j++)
for (int k=cnt[id[j]];k>=1 && col[id[j]][k]==i;k--)
move(id[j],id[n+1]);
swap(id[n+1],id[i]);
}
for (int i=cnt[id[n+1]];i>=1;i--)
if (cnt[id[n-1]]<m) move(id[n+1],id[n-1]);
else move(id[n+1],id[n]);
for (int i=cnt[id[n-1]];i>=1;i--)
if (col[id[n-1]][i]==n) move(id[n],id[n+1]);
for (int i=cnt[id[n-1]];i>=1;i--)
if (col[id[n-1]][i]==n) move(id[n-1],id[n]);
else move(id[n-1],id[n+1]);
for (int i=cnt[id[n]];col[id[n]][i]==n;i--)
move(id[n],id[n-1]);
for (int i=cnt[id[n+1]];col[id[n+1]][i]!=n && cnt[id[n-1]]<m;i--)
move(id[n+1],id[n-1]);
for (int i=cnt[id[n+1]];i>=1;i--)
move(id[n+1],id[n]);
for (int i=cnt[id[n-1]];col[id[n-1]][i]!=n;i--)
move(id[n-1],id[n+1]);
for (int i=cnt[id[n]];i>=1;i--)
if (col[id[n]][i]==n) move(id[n],id[n-1]);
else move(id[n],id[n+1]);
printf("%d
",tot);
for (int i=1;i<=tot;i++)
printf("%d %d
",ans[i][0],ans[i][1]);
return 0;
}