杭電 漢諾塔問題總結
看了一下杭電的各種漢諾塔問題,遇到些奇奇葩葩的小問題,也有很多很好的思想,比如最後一題,來來回回的顛倒很有意思。總結一下;
意思是給把原始的漢諾塔問題中的3根柱子改為4根。做了半天各種WA。查了一下,有一篇文章詳細講了一下,還做出了遞迴公式以及數學公式:
程式碼如下:
#include<cstdio> #include<cmath> #include<algorithm> //F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n) using namespace std; int main() { int n,m; long long Min,f[65]; f[1]=1; f[2]=3; for(int i=3;i<=65;i++) { Min=99999999; for(int j=1;j<i;j++) Min=min(2*f[j]+pow(2.0,i-j)-1,Min*1.0); f[i]=Min; } while(~scanf("%d",&n)) printf("%lld\n",f[n]); return 0; }
按照公式寫就是了,寫的時候需要注意一下精度問題。2^r可以寫為1<<r,不過因為數字常數1預設是32位,所以如果要使用位移的話,一定要先宣告一個longlong型別的變數來進行位移,否則 就會出現溢位錯誤,這個我糾結了一陣子,感覺沒錯,一提交就WA,然後試了試64發現果然是負數。
哎,基礎還是不紮實啊。
用pow函式因為返回的是一個double型別,min函式裡比較也是用double來做的,只是在最後賦值的時候取int型就可以,所以不會出錯。
Pro.ID 1995 漢諾塔V
這個是普通的漢諾塔,最優的步數是2^n-1,只不過問的第i個盤子移動的次數。依然是用遞迴,在紙上畫畫就能出來。
注意了,第i盤子,不用考慮底下的盤子,只用看之上的經過一個柱子到達目的地。即F[n]=2*f[n-1]
程式碼:
Pro.ID 漢諾塔VI#include<iostream> using namespace std; int main() { __int64 s[61] = {0, 1}; int n, i, t, m; for(i = 2; i < 61; i++) s[i] = s[i - 1] * 2; cin >> t; while(t--) { cin >> n >> m; cout << s[n - m + 1] << endl; } }
是問所有步驟,注意不是最優的,是全部(當然不包括錯誤的步驟)
每一個盤子可以放到3根柱子的任意一個,所以是3^n。比如正確的是直接從a->c,現在可以a->b然後在b->c,就是多了2種。每一個都多了2種,所以是3^n。
程式碼:
#include<iostream>
#include<math.h>
#include<stdio.h>
using namespace std;
int main()
{
__int64 t,n,i;
__int64 sum,a[100]={3,};
while(cin>>t)
while(t--)
{
cin>>n;
for(i=1;i<n;i++)
a[i]=a[i-1]*3;
cout<<a[n-1]<<endl;
}
return 0;
}
Pro.ID 1997 漢諾塔VII
題目是說,給定某一時刻的三個柱子上的盤子,問這個是不是符合最優解過程中某一時刻的狀態。
思想是:
對一個含有n個盤子,從A柱移動到C柱藉助B柱的漢諾塔,第n個盤子移動到C柱過程是這樣子的:首先將其餘的n-1個盤子移動到B柱,然後第n個盤子直接移動到C柱。在這過程中,第n個盤子只出現在A柱和C柱兩個柱子上,也即第n個盤子不可能出現在B柱上。因此對於當前移動的盤子,只需檢查其所在的柱子是否符合這個要求,如果出現在B柱上,則顯然進入了錯誤移動中。這是本題求解思想精髓所在。
程式碼就不貼了,上邊這個部落格裡寫的很詳細。
Pro.ID 2064 漢諾塔III
還是遞推:num[i]=3*num[i-1]+2;
不解釋了,程式碼如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
unsigned long long num[36];
int main()
{
num[1]=2;
num[2]=8;
for(int i=3;i<=35;i++)
num[i]=3*num[i-1]+2;
int n;
while(~scanf("%d",&n))
cout<<num[n]<<endl;
return 0;
}
Pro.ID 2077 漢諾塔IV(參考了zz_zigzag的部落格)
好無聊啊,把上邊的規則給改了,只是最大的可以放上邊。其實感覺這個題目跟之前那個1027題目有點想通之處,在1027中說的是4根柱子,所以通用的這3個步驟其實並非最優解:
(1)從1柱藉助3…M柱子將n-(M-2)個盤子移動到2柱上。
(2)將M-2個通過3…M-1柱簡單的移動到M柱上【2*(M-2)-1步驟】。
(3)從2柱藉助1,3…M-1柱子將n-(M-2)個盤子移動到M柱上。
如果有n個盤子,則需要前n-1個挪到中間的盤子上,然後最大的盤子挪到最右面,需要兩步,把前(n-1)個盤子從左邊挪到中間是和從中間挪到右邊需要相同的次數。而a陣列中存放的就是那個前n-1個盤子挪動到相同位置需要的次數。結果即為a[i-1]*2+2。所以我直接想成了是f[n]=2*f[n-1]+2,結果錯了。【因為是需要n-1個盤子前進一步】
而求a陣列需要用到遞推。公式為第i個為前n-1個移動次數的三倍加一,簡化到兩個盤子,小的先移動兩次到最右邊,大的移到中間,然後小的在移回中間,小的移動了三次,而大的移動了一次,就使他們全部挪動了一個位置
所以程式碼如下:
#include<stdio.h>
int a[20]={0,1};
int main()
{
int i,T;
for(i=2;i<21;i++)
{
a[i]=3*a[i-1]+1;
}
scanf("%d",&T);
while(T--)
{
scanf("%d",&i);
printf("%d\n",2*a[i-1]+2);
}
return 0;
}
Pro.ID 2175 漢諾塔IX
普通漢諾塔,問在最優步驟中的第i步是哪一個盤子,跟1995那個題目剛好相反。不過這個有點像數論題。
這樣想,假設是4個盤子,考慮第三個,在第4步的時候將3盤從A移動到C【設目的地是B】,此時1,2盤在B上,設時間為T,然後將1,2盤移動到C上,(需要3步)再把4盤移動到B上,此時的格局為4盤在B上,1,2,3,在C上,距T過去了1+3=4步,那麼3號盤什麼時候再動呢?把1,2移走,3就可以放到B上了,移走1,2需要花費3個步驟,因此距T4+3+1也就是第8步,總體是第12步時,3號盤子會再次移動。現在看明白了吧,就是基數倍的2^(i-1)時,i號盤子會移動。
程式碼如下:
#include<iostream>
using namespace std;
int main()
{
__int64 a[65];
a[1]=1;
__int64 i,n,r;
__int64 m;
for(i=2;i<=63;i++)
a[i]=2*a[i-1];
while(~scanf("%I64d%I64d",&n,&m),n+m)
{
for(i=1;i<=n;i++)
{
r=m/a[i];
if(r%2==1&&m%a[i]==0)
printf("%I64d\n",i);
}
}
return 0;
}
Pro.ID 2184 漢諾塔VIII
感覺這個題目非常的好,挺有意思的,問普通漢諾塔,N個盤子,在最優解的第M步時,每個柱子上的盤子的狀態。
想了半天,也沒什麼思路,但有一點是絕對可以確定的,就是一般解簡單漢諾塔過程的問題都是使用遞迴,可以得到全部過程,但是當N稍微到10以上的時候必然遞迴很慢,所以直接遞迴模擬必然是錯誤的,但是根據上一題目,第i個步移動哪一個盤子中確定的,第K個盤子在 奇數*2^(K-1)時移動可以得到些思想,必然是根據步數來確定盤子。
但是想了老半天也不太清楚那個遞迴改怎麼寫,好像每一次判斷都要做除法到是可以確定某一個盤子,但是如何確定所有的盤子呢?糾結啊
查了半天,找到了一個大神的程式碼。
講的非常的詳細,我把思路以及程式碼粘過來大家分享一下:
/*
定義陣列a,其中a[i]表示完成一次i層漢諾塔移動的次數。
指標o,p,q分別表示三個位置。
起始狀態為n層都在o上,要往q方向移動。
然後分成兩種情況:
1、
m<=a[n-1];
此時,第n層沒機會移動,那麼就相當於o上的n-1層往p上移動。
使其狀態和起始狀態一致,我們要交換p和q。
2、
m>a[n-1];
此時,先進行到下面狀態,上面n-1層移動到p位置,第n層移動到q位置,消耗了a[n-1]+1次移動。
接下來就變成p上的n-1層往q上移動,只要交換o,p,令m=m-a[n-1]-1即可。
通過上述操作,都可以得到第n層的位置,並且問題變成n-1層都在o上,要往q方向移動。
*/
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int main()
{
unsigned __int64 m,a[64];
int row[3][66];
a[1]=1;
a[0]=0;
for(int i=2;i<=63;i++)
a[i]=a[i-1]*2+1;
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d %I64u",&n,&m);
int *start,*mid,*end;
start=row[0];
mid=row[1];
end=row[2];
*start=*mid=*end=1;
while(n)
{
n--;
if(m<=a[n])
{
*(start+*start)=n+1;//從第二個位置開始記錄盤子
(*start)++;//第一個位置表示的是這個柱子一共有多少個盤子
swap(end,mid);
}
else
{
*(end+*end)=n+1;
(*end)++;
swap(start,mid);
m-=(a[n]+1);
}
}
for(int i=0;i<3;i++)
{
printf("%d",row[i][0]-1);
for(int j=1;j<row[i][0];j++)
printf(" %d",row[i][j]);
printf("\n");
}
}
return 0;
}
Pro.ID 漢諾塔 X
進一步加強條件,在求第m步時是哪個盤子動,怎麼動。
必然遞迴啊。把上上個題目修改就可以了。具體的就不多說了,在註釋裡有詳細解釋
#include<iostream>
using namespace std;
__int64 a[65];
void solve(int n,__int64 m,int start,int end)
{
int third=6-start-end;//得到第3跟柱子
__int64 mid=a[n];
if(m==mid) //如果是當前盤子移動,直接從start-->end
{
printf("%d %d %d\n",n,start,end);
return ;
}
if(m<mid)//當前盤子無法移動,必然是上邊的某個盤子動,並且移動一定是到third號柱子上,遞迴求解
solve(n-1,m,start,third);
else//需要先移動當期盤子下部的盤子(參考2184題目)
solve(n-1,m-mid,third,end);
}
int main()
{
__int64 m;
a[1]=1;
int n;
for(int i=2;i<=63;i++)
a[i]=2*a[i-1];
int t;
while(~scanf("%d",&t))
{
while(t--)
{
scanf("%d%I64d",&n,&m);
solve(n,m,1,3);
}
}
return 0;
}
最後一個Pro.ID 2587:很O_O的漢諾塔
真心是跪了,