【牛客小白月賽21】NC201605 Bits

【牛客小白月賽21】NC201605 Bitsios

題目描述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

相關文章
相關標籤/搜索