「NOIP2020」移球遊戲 題解 (構造,分治)
原本剛聽完課時是不想寫這道題的……
題目簡介
\(N+1\)根柱子,其中 \(N\) 個上面各有\(M\)個小球,第 \(N+1\) 根柱子是空的。小球共 \(N\) 種顏色。
現在要使得每根柱子上的小球顏色相同,求移動次數與方法(不必最優)。
分析
考慮將第 \(1\) 根柱子全部放 \(1\) 色球,第 \(2\) 根柱子全部放 \(2\) 色球,\(\dots\),第 \(N\) 根柱子全部放 \(N\) 色球。
首先考慮 \(N=2\) 的情況:
現在第 \(1\) 根柱子中找到顏色為 \(2\) 的球的個數 \(k\),那麼顏色為 \(1\) 的球有 \(M-k\) 個;
將第 \(2\)
將柱子 \(1\) 清空,顏色為 \(2\) 的球掛到 \(2\) 號柱上去,顏色為 \(1\) 的球掛在 \(3\) 號柱上去,現在第 \(1\) 根柱子沒有球,第 \(2\) 和第 \(3\) 根柱子上都有 \(M\) 個球,且第 \(2\) 根柱子頂端 \(k\) 個球顏色都為 \(2\),第 \(3\) 根柱子頂端 \(M-k\) 個球顏色都為 \(1\)。
將第 \(3\) 根柱子頂端 \(M-k\) 個球放回 \(1\) 號柱,將第 \(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;
}