「NOIP2020」移球游戏 题解 (构造,分治)

原本刚听完课时是不想写这道题的……

题目简介

\(N+1\)根柱子,其中 \(N\) 个上面各有\(M\)个小球,第 \(N+1\) 根柱子是空的。小球共 \(N\) 种颜色。

现在要使得每根柱子上的小球颜色相同,求移动次数与方法(不必最优)。

分析

考虑将第 \(1\) 根柱子全部放 \(1\) 色球,第 \(2\) 根柱子全部放 \(2\) 色球,\(\dots\),第 \(N\) 根柱子全部放 \(N\) 色球。

首先考虑 \(N=2\) 的情况:

现在第 \(1\) 根柱子中找到颜色为 \(2\) 的球的个数 \(k\),那么颜色为 \(1\) 的球有 \(M-k\) 个;

将第 \(2\) 根柱子顶端 \(k\) 个球移动到第三根柱子上,现在第 \(2\) 根柱子上有 \(M-k\) 个球,第 \(3\) 根柱子上有 \(k\) 个球;

将柱子 \(1\) 清空,颜色为 \(2\) 的球挂到 \(2\) 号柱上去,颜色为 \(1\) 的球挂在 \(3\) 号柱上去,现在第 \(1\) 根柱子没有球,第 \(2\) 和第 \(3\) 根柱子上都有 \(M\) 个球,且第 \(2\) 根柱子顶端 \(k\) 个球颜色都为 \(2\),第 \(3\) 根柱子顶端 \(M-k\) 个球颜色都为 \(1\)

将第 \(3\) 根柱子顶端 \(M-k\) 个球放回 \(1\) 号柱,将第 \(2\) 根柱子顶端 \(k\) 个球放回 \(1\) 号柱。现在第 \(1\) 根柱子下方 \(M-k\) 个都是 \(1\) 色球,上方 \(k\) 个都是 \(2\) 色球。

将第 \(2\) 根柱子剩下的 \(M-k\) 个球全部放到 \(3\) 号柱上,再将第 \(1\) 根柱子上方 \(k\)\(2\) 色球全部放到 \(2\) 号柱上,现在 \(1\) 号柱有 \(M-k\)\(1\) 色球,\(2\) 号柱上有 \(k\)\(2\) 色球,\(3\) 号柱上有 \(M\) 个杂色球。

清空 \(3\) 号柱,将 \(1\) 色球全部挂 \(1\) 号柱,\(2\) 色球全部挂 \(2\) 号柱,这样就完了。

在考虑 \(N>2\) 的情况:

考虑分治,将球的颜色分为 \(\leq mid\)\(>mid\) 考虑。

对于区间 \([l,r]\) ,我们将颜色 \(\leq mid\) 的全部移动到 \([l,mid]\) , 将颜色 \(> mid\) 的全部移动到 \([mid+1,r]\),这个过程可以看作对两种颜色处理。

在区间 \([l,mid]\) 中寻找第一根含有颜色 \(>mid\) 的柱子,在 \([mid+1,r]\) 中寻找第一根含有颜色 \(\leq mid\) 的柱子,根据上述 \(N=2\) 的过程移动,注意:两根柱子上不合法的小球数量可能不一样,移动之后不一定能刚好解决,多余的放不下的小球,留在相反的柱子上下次解决(详见代码)。

处理完 \([l,r]\) 后,\([l,mid]\) 上全是颜色 \(\in [l,mid]\) 的小球,\([mid+1,r]\) 上全是颜色 \(\in [mid+1,r]\) 的小球,于是我们又可以分开处理区间 \([l,mid]\) 和区间 \([mid+1,r]\) 了。

可以由此将 \(N>2\)\(N=2\) 的情况并成 \(N\geq 2\)

每一次操作都要移动 \(5M-k\) 次,总共移动约为 \(5MN\times \log N\)

代码实现较长,建议多用函数,注意细节:

\(AC\ Code\)

#include<cstdio>
#include<iostream>
using namespace std;
int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
int a[55][405];
int p[55];
int cnt[55];
const int Maxn=1e6+5;
struct Record{
	int from;
	int to;
}g[Maxn];
int tot;
int n,m;
void count(int l,int r){
	const int mid=(l+r)>>1;
	for(int i=l;i<=mid;i++){
		cnt[i]=0;
		for(int j=1;j<=p[i];j++)
			if(a[i][j]>mid)cnt[i]++;
	}
	for(int i=mid+1;i<=r;i++){
		cnt[i]=0;
		for(int j=1;j<=p[i];j++)
			if(a[i][j]<=mid)cnt[i]++;
	}
}
inline void moving(int x,int y){
	a[y][++p[y]]=a[x][p[x]--];
	g[++tot].from=x;
	g[tot].to=y;
}
void move(int lp,int rp,const int c){
	int k=cnt[lp];
	for(int i=1;i<=k;i++)moving(rp,n+1);
	for(int i=1;i<=m;i++){
		if(a[lp][p[lp]]<=c)moving(lp,n+1);
		else moving(lp,rp);
	}
	for(int i=1;i<=m-k;i++)moving(n+1,lp);
	for(int i=1;i<=k;i++)moving(rp,lp);
	for(int i=1;i<=m-k;i++)moving(rp,n+1);
	for(int i=1;i<=k;i++)moving(lp,rp);
	cnt[lp]=cnt[rp]=0;
	for(int i=1;i<=m;i++){
		if(a[n+1][p[n+1]]<=c){
			if(p[lp]<m)moving(n+1,lp);
			else moving(n+1,rp),cnt[rp]++;
		}else{
			if(p[rp]<m)moving(n+1,rp);
			else moving(n+1,lp),cnt[lp]++;
		}
	}
}
void solve(int l,int r){
	if(l>=r)return ;
	const int mid=(l+r)>>1;
	count(l,r);
	int lp=l,rp=r;
	while(lp<rp){
		while(!cnt[lp]&&lp<=mid)lp++;
		while(!cnt[rp]&&rp>mid)rp--;
		if(lp>mid||r<=mid)break;
		move(lp,rp,mid);
	}
	solve(l,mid);
	solve(mid+1,r);
}
int main(){
	n=read();
	m=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][++p[i]]=read();
	solve(1,n);
	printf("%d\n",tot);
	for(int i=1;i<=tot;i++)
		printf("%d %d\n",g[i].from,g[i].to);
	return 0;
}

$$-----EOF-----$$

posted @ 2022-02-13 18:51  AlienCollapsar  阅读(91)  评论(2编辑  收藏  举报
// 生成目录索引列表 // ref: http://www.cnblogs.com/wangqiguo/p/4355032.html // modified by: zzq