【洛谷P7115】移球游戏

题目

题目链接:https://www.luogu.com.cn/problem/P7115
小 C 正在玩一个移球游戏,他面前有 \(n + 1\) 根柱子,柱子从 \(1 \sim n + 1\) 编号,其中 \(1\) 号柱子、\(2\) 号柱子、……、\(n\) 号柱子上各有 \(m\) 个球,它们自底向上放置在柱子上,\(n + 1\) 号柱子上初始时没有球。这 \(n \times m\) 个球共有 \(n\) 种颜色,每种颜色的球各 \(m\) 个。

初始时一根柱子上的球可能是五颜六色的,而小 C 的任务是将所有同种颜色的球移到同一根柱子上,这是唯一的目标,而每种颜色的球最后放置在哪根柱子则没有限制。

小 C 可以通过若干次操作完成这个目标,一次操作能将一个球从一根柱子移到另一根柱子上。更具体地,将 \(x\) 号柱子上的球移动到 \(y\) 号柱子上的要求为:

  1. \(x\) 号柱子上至少有一个球;
  2. \(y\) 号柱子上至多有 \(m - 1\) 个球;
  3. 只能将 \(x\) 号柱子最上方的球移到 \(y\) 号柱子的最上方。

小 C 的目标并不难完成,因此他决定给自己加加难度:在完成目标的基础上,使用的操作次数不能超过 \(820000\)。换句话说,小 C 需要使用至多 \(820000\) 次操作完成目标。

小 C 被难住了,但他相信难不倒你,请你给出一个操作方案完成小 C 的目标。合法的方案可能有多种,你只需要给出任意一种,题目保证一定存在一个合法方案。

思路

假设我们已经处理好了 \(1\sim 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 \to 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\) 球到了柱子 \(1\sim 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\n",tot);
	for (int i=1;i<=tot;i++)
		printf("%d %d\n",ans[i][0],ans[i][1]);
	return 0;
}
posted @ 2020-12-12 11:47  stoorz  阅读(189)  评论(0编辑  收藏  举报