題目描述ide
Nancy喜歡作遊戲!
漢諾塔是一個神奇的遊戲,神奇在哪裏呢?
給出3根柱子,最開始時n個盤子按照大小被置於最左的柱子。
若是盤子數爲偶數,則須要將她們所有移動到最右側的柱子上,不然將她們移動到中間的柱子上。
那麼,Nancy該怎樣移動呢?請你輸出漢諾塔遊戲的過程叭!spa
輸入描述:
共一行:一個整數ncode
一不當心拿了個運行時間最快qwq(8ms)(截至2020-01-26)視頻
在別的題解中看到用二進制來解漢諾塔問題這個視頻,又看大多數人都是用遞歸寫的,便想試一試二進制作法。這個視頻說的就是漢諾塔與二進制的關係(從視頻的第6分鐘開始)。遞歸
我對這個視頻的大體理解就是,在二進制中,好比想從0000加到1111,那就要先加111到0111,再加1得1000,再加11到1111,這與漢諾塔的遞歸策略很類似,想移動n個盤必須先用f[n-1]步移動n-1個到第二個柱,再用1步把第n個移到第三個柱,最後再用f[n-1]步移動n-1個到第三個柱,這顯然是最優解,沒有任何步數的浪費(其實這就得出了漢諾塔的遞推式:f[n]=f[n-1]*2+1)。遊戲
因此,咱們徹底能夠經過枚舉二進制來模擬漢諾塔的移動,若有n個盤,那咱們就要從1枚舉到2^n-1(其實n盤漢諾塔的最少移動次數f[n]就等於2^n-1),對於每一個數,咱們看它最後一個1在第幾位,如在第num位,就把編號爲n的盤向右移動到下一個可行的柱子(柱子3的下一個柱子是柱子1)上。你想操做第n位,必須等第n位是最後一位,也就是要把後面的1消除,這與漢諾塔的最優解規律是一致的:若是你想移動第n個盤,必須等這個盤子上沒有其餘盤,也就是移去上面的盤,因此這種移動的二進制規律確定是最優解。ci
掌握了這個思路,接下來就是耐心考慮怎麼輸出圖案了qwq字符串
#include<math.h> #include<string> #include<iostream> using namespace std; int t[3][12],count[3],n,m,width; //t[i][j]存儲第i號柱的第j層的盤的編號(柱從0開始編號),count是柱上的盤子數,width=2*n+1,即最大盤的寬度 int order[6]= {0,1,2,0,1,2},number[11]; //order是塔的順序,因爲要向右移動到下一個可行的柱子上,柱子編號超過2要回到編號0,number[i]是編號爲i的盤所在的柱子 string _begin,_str,str; //_begin是每組圖形的開頭部分,_str是「...|.....|.....|...」,str用於輸出每行的實際狀況 inline void print() { for (int floor=n,pos,len;floor;floor--){//從第n層向下依次輸出 str=_str;//初始化爲「...|.....|.....|...」 for (int i=0;i<3;i++){//畫上每一個柱此層的盤子 if (count[i]>=floor){//若是有盤子 len=2*t[i][floor]+1;//盤子寬 pos=width*i+i+1+(width-len)/2;//畫盤子的起始位置,width最大盤的寬度,即柱子間的距離,注意柱子間還會多空出一位 for (int j=pos+1;j<=pos+len;j++) str[j-1]='*';//畫上盤子,因爲是字符串,j減1 } } cout<<str<<"\n"; } } int main() { ios::sync_with_stdio(false); cin>>n,m=6*n+7;//m是行的長度 for (int i=1; i<=n; i++) t[0][i]=n-i+1; count[0]=n;//把n個盤放在第0個柱上 for (int i=0; i<m; i++) _begin+='.',_str+='.'; width=2*n+1; _str[width/2+1]=_str[width+2+width/2]=_str[width*2+3+width/2]='|';//畫上中間的「|」 _begin+="\n"+_str+"\n"; cout<<_begin; //這裏的begin是「...................」,第一行沒有「-------------------」 print();//輸出第一組圖形 string tmp; for (int i=0; i<m; i++) tmp+='-'; _begin=tmp+"\n"+_begin; //這裏的begin是「------------------- ...................」兩行 for (register int i=1,lim=(1<<n); i<lim; i++) {//從1枚舉到2^n-1 int num=log2(i&-i)+1,_number=number[num];//i&-i是最後一位1加上後面的0所表示的數,用log2求出它的二進制位數,num便是要移動的盤子編號,_number是此盤所在柱子 cout<<_begin; for (int j=1,k; j<3; j++) {//尋找移動到哪個柱子上 k=order[_number+j];//k是柱子的編號 if (t[k][count[k]]>num || !count[k]) {//若是柱上最後一個盤大於num或柱上沒有盤,則可行 number[num]=k,t[k][++count[k]]=num; count[_number]--;//移動盤子,更新信息 break; } } print(); } }
若是各位大佬有更好的理解或想法,歡迎提出改進和建議qwq!get