1. 程式人生 > 其它 >「NOIP2020」移球遊戲 題解 (構造,分治)

「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-----$$