NOIP2020 移球遊戲
Description
給定 \(n+1\) 個棧,前 \(n\) 個棧內有不定的 \(m\) 個元素,最後一個棧為空,每個棧的最大容量為 \(m\)
每種顏色都有 \(m\) 種,求任意一種方法,使得在 \(820000\) 次操作內把相同的元素都移動到同一個棧內
Solution
考慮移動單個元素
列舉元素種類,設當前列舉到的元素種類為 \(Now\)
移動規則如下
-
統計第一個柱子上元素 \(Now\) 的數量 \(Count\)
-
從第 \(Now\) 個棧移動 \(Count\) 個元素到第 \(Now+1\) 個棧上(為了預留出位置存放元素 \(Now\))
-
把第一根柱子的元素分離,是 \(Now\)
-
從棧 \(Now+1\) 移動 \(m-Count\) 個元素到第一個柱子(為第二個柱子讓位)
-
把第二個棧內不是 \(Now\) 的元素移動到第一個棧上,放不開了就放到第 \(Now+1\) 個棧內
-
\(\text{swap}\) 分別交換第一和第 \(Now\),第二和第 \(Now+1\) 個棧(這樣不停操作第一二個棧和列舉的棧 \(Now\) 就可以了)
-
然後 \(k\) 列舉第一到第 \(Now\) 個棧,分別統計他們裡面元素 \(Now\) 的數量,然後把第 \(Now\) 個棧移走相同數量的元素到 \(Now+1\)
-
分離當前列舉到的棧內的元素,把元素 \(Now\) 都放到棧 \(Now\) 內,其他的都放到棧 \(Now+1\) 內
-
\(\text{swap}\) 分別交換第 \(k\) 和第 \(Now\),第 \(k\) 和第 \(Now+1\) 個棧(證明棧 \(Now\) 已被處理完,之後不會再對其操作)
-
然後把第一到第 \(Now\) 個棧上方的 \(Now\) 元素都移到棧 \(Now+1\) 上,放上棧 \(Now\) 內的元素 \(Now\)(此時棧 \(Now\) 上方全是元素 \(Now\))
如此,可以處理玩所有的顏色
然而,這個方法並不適用於 \(n=2\)
原因是當列舉第一個顏色時,第 \(Now+1\) 個棧就是第二個棧
所以要特判處理
移動規則與 \(n\geq3\) 時大同小異
無非是
-
統計第一棧內元素 \(1\) 的個數,然後從第二棧移動相同的數量到第三棧
-
分離第一棧,把元素 \(1\) 放到第二棧上,其他的放到第三棧上
-
然後把第二棧上的元素 \(1\) 移回第一棧,使第一棧此時只有元素 \(1\)
-
從第三棧移動 \(m-Count\) 個元素到第一棧,剩下的移回第二棧
-
把那 \(m-Count\) 個元素移回去
-
分離第二棧,是 \(1\) 的放回第一棧,不是的放回第三棧
因為一種只有兩種元素,且可以確定第一棧全為 \(1\) ,第三棧全為 \(2\)
所以至此問題得到解決
極限操作次數為 \(\sum_{i=1}^n im + 5m\),大概需要 \(600000\) 次,時間複雜度同操作次數
Code
#include<bits/stdc++.h>
#define rr register
#define maxn 410
#define maxm 850010
using namespace std;
int n,m,cnt[maxn],fr[maxm],to[maxm];
int Col[maxn][maxn],P[maxn];
int Ans;//移動次數
//Col[i][j]第 i 根柱子上的第 j 個球的顏色
//P[i]第 i 跟柱子
//cnt[i]當前柱子上球的數量
inline int Read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*w;
}
inline int Get_Count(int x,int y){
int ans=0;//統計在第 x 列第 y 種顏色的數量
for(int i=1;i<=m;i++) if(Col[x][i]==y) ans++;
return ans;
}
inline void Move_Ball(int x,int y){
fr[++Ans]=x;to[Ans]=y;//把第 x 列最上方的球移動到第 y 列
Col[y][++cnt[y]]=Col[x][cnt[x]--];
}
inline int Top(int x){return Col[x][cnt[x]];}//求柱子最上方的球
int main(){
n=Read();m=Read();
for(rr int i=1;i<=n;i++){cnt[i]=m;for(rr int j=1;j<=m;j++) Col[i][j]=Read();}
for(rr int i=1;i<=n+1;i++) P[i]=i;cnt[n+1]=0;
for(rr int Now=n;Now>=3;Now--){//只有 2 根柱子的時候情況與方法不符,所以分開處理
int Count=Get_Count(P[1],Now);
//找第一根柱子上當前顏色的數量,然後把第 Now 根移動相同的數量到第 Now + 1 根上去,為第一根讓位
for(rr int i=1;i<=Count;i++) Move_Ball(P[Now],P[Now+1]);
for(rr int i=1;i<=m;i++) if(Top(P[1])==Now) Move_Ball(P[1],P[Now]);else Move_Ball(P[1],P[Now+1]);
//把第一根柱子上的球分離顏色,如果是第 Now 種顏色就移動到第 Now 根柱子上,否則就移動到 Now + 1 上
for(rr int i=1;i<=m-Count;i++) Move_Ball(P[Now+1],P[1]);//把 m - Count 個球移回第 1 根柱子,為第二根讓位
for(rr int i=1;i<=m;i++) if(Top(P[2])==Now||cnt[P[1]]==m) Move_Ball(P[2],P[Now+1]);else Move_Ball(P[2],P[1]);
//把第 2 根柱子上的不是 Now 的球移動到第一根上,放不開就放第 Now + 1 根,從而使第一根柱子上不存在 Now
swap(P[1],P[Now]);swap(P[2],P[Now+1]);//處理過的移走,方便下次處理其他的柱子
for(rr int k=1;k<Now;k++){
Count=Get_Count(P[k],Now);
for(rr int i=1;i<=Count;i++) Move_Ball(P[Now],P[Now+1]);
for(rr int i=1;i<=m;i++) if(Top(P[k])==Now) Move_Ball(P[k],P[Now]);else Move_Ball(P[k],P[Now+1]);
swap(P[k],P[Now+1]);swap(P[k],P[Now]);//分離前 Now 根柱子上的顏色,操作同上
}
for(rr int i=1;i<Now;i++) while(Top(P[i])==Now) Move_Ball(P[i],P[Now+1]);//把能分離的 Now 都放到柱子 Now + 1 上
for(rr int i=1;i<Now;i++) while(cnt[P[i]]<m) Move_Ball(P[Now],P[i]);//此時柱子 Now 上面的顏色全是 Now
}
int Count=Get_Count(P[1],1);//只有兩根柱子的情況
for (rr int i=1;i<=Count;i++) Move_Ball(P[2],P[3]);
for (rr int i=1;i<=m;i++) if (Top(P[1])==1) Move_Ball(P[1],P[2]);else Move_Ball(P[1],P[3]);
for (rr int i=1;i<=Count;i++) Move_Ball(P[2],P[1]);
for (rr int i=1;i<=m-Count;i++) Move_Ball(P[3],P[1]);
while (cnt[P[3]]) Move_Ball(P[3],P[2]);
for (rr int i=1;i<=m-Count;i++) Move_Ball(P[1],P[3]);
for (rr int i=1;i<=m;i++) if (Top(P[2])==1) Move_Ball(P[2],P[1]);else Move_Ball(P[2],P[3]);
printf("%d\n",Ans);for(int i=1;i<=Ans;i++) printf("%d %d\n",fr[i],to[i]);
return 0;
}