HDU——汉诺塔问题汇总整理


基本汉诺塔问题

有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

void Hanoi(char from,char mid,char to,int n){
    if(n==1){
        printf("%c->%c\n",from,to);
        return ;
    }
    //n-1个到中转
    Hanoi(from,to,mid,n-1);
    //最后一个到目标
    printf("%c->%c\n",from,to);
    //n-1个到目标
    Hanoi(mid,from,to,n-1);
}

汉诺塔II——四根柱子

1207

三根柱子的时候:移动次数为2^N-1,问:四根柱子时,最少移动次数是多少?N(1<=N<=64)。

设F[n]表示将n个盘从按规则从a柱移到b柱至少需要移动的次数。

  • 三根柱子:(a到b,c为中间柱)

    n==1 时:F[ 1 ]= 1;
    n>1 时 :
    第一步:将a柱上的n-1个移动到c:F[n-1]
    第二步:把最底下的一个移到b:1
    第三步:用相同的方法操作n-1个:F[n-1]
    F[n]= 2*F[n-1]+1

  • 四根柱子:(a移到b,c,d为中间柱)

    F[1]= 1;
    F[2]= 3;
    假设分x个盘子到c柱上
    第一步:将a柱上的x个依靠b和d移动到c:F[x]次
    第二步:a柱剩下n-x个,因为c上的小,所以只能依靠d柱移到b柱上:三柱的次数:2^(n-x)+1次
    第三步:接下来移动c上的,a和d都为空,借助a,d移到b:F[x]次

    F[n]
    =F[x]+2^ (n-x)-1+F[x]
    = 2*F[x]+2^(n-x)-1
    易随着x的不同取值,对于同一个n,也会得出不同的F[n]。
    因为1<=x<=n,答案应该遍历1~n,取min{ 2 * F[x]+2 ^ (n-x)-1}。

用memset(a,0,sizeof(a))想将某个数组全部赋值为无穷大时
是因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0
如果我们将无穷大设为0x3f3f3f3f,0x3f3f3f3f的每个字节都是0x3f
所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。

关于double类型的memset填充问题
常规做题需要
我们知道,很多时候我们用到double范围的需求,和int是一样的
max:0x42 min:0xc2
用很大的数值推荐配置:
max:0x7f
min:0xfe

数组开成int ll 不能用min了,原因:用了pow函数,pow函数返回值是double,而min函数需要比较的数类型一致

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int N = 65;
double F[N];
void hanoi(){
    memset(F,0x7f,sizeof(F));  //出错:原因
    F[0] = 0;
    F[1] = 1;
    F[2] = 3;
    for(int n=3; n<N; n++)
        for(int x=1; x<n; x++){
            F[n] = min(F[n], 2*F[x] + pow(2,n-x) - 1);
        }
}
int main(){
    hanoi();
    int n;
    while(~scanf("%d",&n)){
        cout << F[n] << endl;
    }
    return 0;
}


汉诺塔III ——必须借助中间柱

2064

目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。
包含多组数据,每次输入一个N值(1<=N=35)。

F[1] = 2;
F[2] = 8;
F[3] = 26;

目的:一个个来,最重的移到最右边,此时所有都在最右边,最重的移动到中间,其余再依次移到最左边,与开始情况相同,最重的移动到最右。

(1):n-1个移动到中间 F[n-1]
(2):n-1个移动到最右边 F[n-1]
(3):最重的移动到中间 1
(4):n-1个移回最左边 F[n-1]
(5):最重的移动到最右边 1

F[n] = 3*F[n-1] + 2;

由递推公式求通项式:
F[n]+1 = 3(F[n-1] + 1) 等比数列
公比为3,首项为3 : (F[1]+1)
所以通项公式为:
F[n] + 1 = 3*3^(n-1)
F[n] = 3^n - 1

自己算几个也可以找出规律

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int N = 36;
ll F[N];
void hanoi(){
    F[1] = 2;
    F[2] = 8;
    for(int i=3; i<N; i++){
        F[i] = 3*F[i-1] + 2;
    }
}
int main(){
    hanoi();
    int n;
    while(~scanf("%d",&n)){
        cout << F[n] << endl;
    }
    return 0;
}

或者:递归

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define M 10010
#define INF 0x3f3f3f3f
using namespace std;
typedef unsigned long long ULL;
ULL hanoi[100];
ULL f(int n,int a,int b, int c){
    if(n==1)
        return 2;
    if(hanoi[n]) return hanoi[n];
    ULL ans = 0;
    //n-1个a->c
    ans += f(n-1,a,b,c);
    //1 -> a->b
    ans ++;
    //n-1 c->b
    ans += f(n-1,c,a,b);
    //n-1 b->a
    ans += f(n-1,b,c,a);
    //1 b->c
    ans++;
    return hanoi[n]=ans;
}
int main()
{
    int n;
    while(~scanf("%d",&n)){
        cout << f(n,0,0,0) << endl;
    }
    return 0;
}

汉诺塔IV——III的升级,中间允许最大盘在上

汉诺塔IV——III的升级,中间允许最大盘在上

还记得汉诺塔III吗?
他的规则是这样的:不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到小盘的上面。
如果我们允许最大的盘子放到最上面会怎么样呢?(只允许最大的放在最上面)当然最后需要的结果是盘子从小到大排在最右边。
输入数据的第一行是一个数据T,表示有T组数据。
每组数据有一个正整数n(1 <= n <= 20),表示有n个盘子。
Output
对于每组输入数据,最少需要的摆放次数。

P[1] = 2;
P[2] = 4;
P[3] = 10;

P[n]
(1)n-2个盘子从A柱通过B柱移动到C柱:F[n-2]
(2)把 n 和 n-1 号盘从A移动到B: 2
(3)把前n-2个盘子从C柱通过B柱移动到A柱:F[n-2]
(4)把 n 和 n-1 号盘从B移动到C: 2
(5)把前n-2个盘子从A通过B移动到C F[n-2]

这里的F[n]是上一题的 F[n] = 3^n - 1
原因:基本的F[n]允许直接移动到最右边,与此题题意不符
P[n] = 3*F[n-2] + 4 = 3 * (3^(n-2) - 1) + 4 = 3 ^ (n-1) + 1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int N = 25;
ll F[N];
void hanoi(){
    F[1] = 2;
    F[2] = 4;
    for(int i=3; i<N; i++){
        F[i] = pow(3,i-1) + 1;
    }
}
int main(){
    hanoi();
    int T,n;
    cin >> T;
    while(T--){
        scanf("%d",&n);
        cout << F[n] << endl;
    }
    return 0;
}

注意:pow运算:在运行10000000次后,计算速度的差距就体现出来了,使用乘法一次次乘的速度最快,大约只有pow函数的35%


汉诺塔V——盘子的移动次数

1995

用1,2,…,n表示n个盘子,称为1号盘,2号盘,…。号数大盘子就大。
告之盘子总数和盘号,计算该盘子的移动次数.

a全部移到b需要2^n-1次
最大号盘,n号盘只需移动1次 2^0 = 2 ^ (n-n)
第二大盘,n-1号盘移动:2次 =2^1=2 ^(n-(n-1))
k号: 2^(n-k)次

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cstdlib>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
const int N = 65;

int main(){
    ll pow[N];
    pow[0] = 1;
    for(int i=1; i<N; i++)
        pow[i] = pow[i-1] << 1;
    int T,n,k;
    cin >> T;
    while(T--){
        cin >> n >> k;
        cout << pow[n-k] << endl;
    }
    return 0;
}

int main(){
    int T;
    int n,k;
    cin >> T;
    while(T--){
        cin >> n >> k;
        cout << (ll)pow(2,n-k) << endl;
    }
    return 0;
}

错误代码: printf("%.lf\n",pow(2,n-k));
——注意:要转换成long long


汉诺塔VI——n个盘子放在3个柱子上的所有情况种数

1996

n个盘子的汉诺塔问题的最少移动次数是2^ n-1,即在移动过程中会产生2^n个系列。
由于发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱子从下往上的大小仍保持如下关系 : n=m+p+q
a1>a2>…>am
b1>b2>…>bp
c1>c2>…>cq
计算所有会产生的系列总数.
包含多组数据,首先输入T,表示有T组数据.每个数据一行,是盘子的数
目N<30.
Output
对于每组数据,输出移动过程中所有会产生的系列总数。
Sample Input
3
1
3
29
Sample Output
3
27
68630377364883

题意:
n个盘子放在3个柱子上的所有情况种数
观察数据,猜测是3的n次方,结果正确

理解:
手里拿着一摞盘子开始放(先放大的,保证大的在下,小的在上)
放第一个:3个柱子挑一个,3
放第二个:3个柱子挑一个,3
放第n个:3
ans = pow(3,n);

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define M 65
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;

int main(){
    int T,n;
    cin >> T;
    while(T--){
        cin >> n;
        cout << (ll)pow(3,n) << endl;
    }
    
    return 0;
}


汉诺塔VII——判断它是否是在正确的移动中产生的系列

1997

Problem Description
n个盘子的汉诺塔问题的最少移动次数是2^ n-1,即在移动过程中会产生2 ^n个系列。由于发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱子从下往上的大小仍保持如下关系 :
n=m+p+q
a1>a2>…>am
b1>b2>…>bp
c1>c2>…>cq
ai是A柱上的盘的盘号系列,bi是B柱上的盘的盘号系列, ci是C柱上的盘的盘号系列,最初目标是将A柱上的n个盘子移到C盘. 给出1个系列,判断它是否是在正确的移动中产生的系列.

题意:
在移动过程中每个柱子上的数量会发生变化(大的在下不变),给出在a,b,c柱上的数量以及盘子的编号,判断当前过程是否是正确移动中产生的系列

汉诺塔三步:
(1)n-1个从a借助c移动到b
(2)最大的从a直接移动到c
(3)n-1个从b借助a移动到c

判断:
(1)首先判断是否都在a或者c ,是:true
(2)n号盘子只能在a或者c,在b则为错
(3)如果n号在a,那么说明此时在进行第一步,考虑上一号n-1盘,看其移动方向是否为a到c
(4)如果n号在c,那么此时在进行第三步,考虑n-1号,n-1移动方向应该为b到c

以上过程递归实现,直到n==0

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define M 70
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
int N;
int p_num[4];
int p[4][M]; //记录123状态 pillar
void scan(){
    scanf("%d",&N);
    for(int k=1; k<=3; k++){    //三根柱子123
        scanf("%d",&p_num[k]);
        for(int i=1; i<=p_num[k]; i++)     //每根柱子上的盘子
            scanf("%d",&p[k][i]);
    }
}
bool check(int n,int a[],int b[],int c[]){
    if(n==0)            //出口
        return true;
    for(int i=1; b[i]; i++){       //n号不能在中间
        if(b[i]==n)
            return false;
    }
    for(int i=1; a[i]; i++){        //n号在a
        if(a[i]==n) //是此时最大的,如果是,往上check比这个小一个多
            return check(n-1,a,c,b);
    }
    for(int i=1; c[i]; i++){       //n在c
        if(c[i]==n)
            return check(n-1,b,a,c);
    }
    return 1;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        for(int k=1; k<=3; k++)
            memset(p[k],0,sizeof(p[k]));
        scan();
        if(p_num[1]==N || p_num[3]==N || check(N,p[1],p[2],p[3]))
            cout << "true" << endl;
        else cout << "false" << endl;
    }
    return 0;
}


注意:递归回去的是这个函数里面的三个数列


汉诺塔VIIII——根据步数倒推摆放

2184

Problem Description
1,2,…,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.
在第1根柱子上的盘子是
a[1],a[2],…,a[n].
a[1]=n,a[2]=n-1,…,a[n]=1.
即a[1]是最下面的盘子.
把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.问移动m次后的状况.

盘子移动次数:
第一个盘子:1,3,5,7
第二个盘子:2,6,10,14
第三个:4,12,20,28

每次移动次数差2 ^ n次
且从2 ^ (n-1)开始是第一次

举例——三号:
第一次:2 ^ (3-1)
第二次:2 ^ (3-1) + 1 * 2 ^ 3
第三次:2 ^ (3-1) + 2 * 2 ^ 3
第四次:2 ^ (3-1) + 3 * 2 ^ 3

第一次移动i号盘:2 ^ (i -1)
第k+1次移动i号:比第一次多k: 2 ^ (i -1) + k * 2 ^ i

盘子的移动方式:
(1)n-1个盘子从a借助c到b
(2)此时的最大号从a到c
(3)n-1从b借助a到c

如果n号盘子移动,说明此时次数m>=2^(n-1)
最大号盘子只能在a或者c

从n号开始倒着看:

如果m>=2^(n-1) 此时最大盘在c,进行(3),查找n-1,此时n-1号盘从b借助a到c,步数m-=2 ^ (n-1)

如果m< 2^(n-1) 此时最大盘子在a,进行(2),查找n-1,此时n-1号盘从a借助c到b,m不变

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define M 65
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
int T,n;
ll m;
vector<int>a,b,c; //三个柱子情况
ll pow2[M]; //存2^n

void move(int n,ll m,vector<int>&a,vector<int>&b,vector<int>&c){
                            //写了&,表示可以返回引用自己
    if(n==0)
        return ;
    if(m>=pow2[n-1]){       
        c.push_back(n);     //当前最大盘位于c
        m -= pow2[n-1];     
        move(n-1,m,b,a,c);  
    }
    else{                  
        a.push_back(n);		//当前最大的盘子在a,放入a,
        move(n-1,m,a,c,b);
    }
}
void init(){
    a.clear();
    b.clear();
    c.clear();
}
void print(vector<int>v){
    printf("%d",v.size());
    if(!v.empty()){
        for(int i=0; i<v.size(); i++)   //不能写v[i]
            printf(" %d",v[i]);
    }
    printf("\n");
}
int main(){
    pow2[0] = 1;
    for(int i=1; i<M; i++)
        pow2[i] = pow2[i-1] << 1;
    scanf("%d",&T);
    while(T--){
        scanf("%d %lld",&n,&m);   //n个盘子,第m次移动后的情况
        init();
        move(n,m,a,b,c);
        print(a);
        print(b);
        print(c);
    }
    return 0;
}


汉诺塔IX——第m次移动的是那一个盘子

2175

Problem Description
1,2,…,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.
在第1根柱子上的盘子是a[1],a[2],…,a[n]. a[1]=n,a[2]=n-1,…,a[n]=1.即a[1]是最下
面的盘子.把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.
问第m次移动的是那一个盘子.
Input
每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1.n=m=0退出
Output
输出第m次移动的盘子的号数.
Sample Input
63 1
63 2
0 0
Sample Output
1
2

第一个盘子:1,3,5,7
第二个盘子:2,6,10,14
第三个:4,12,20,28

每次移动次数差2 ^ n次
且从2 ^ (n-1)开始是第一次

三号:
第一次:2 ^ (3-1)
第二次:2 ^ (3-1) + 1 * 2 ^ 3
第三次:2 ^ (3-1) + 2 * 2 ^ 3
第四次:2 ^ (3-1) + 3 * 2 ^ 3

假设是x号,一共m次,与第一次差k次
2 ^ (x-1) + k * 2 ^ x = m
m - 2 ^ (x-1) = 2 ^ x * k
也就是 : ( m - 2 ^ (x-1) ) % 2 ^ x == 0

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 65;
typedef long long ll;
ll n,m;
ll pow2[maxn];
int main(){
    //2^n
    pow2[0] = 1;
    for(int i=1; i<maxn; i++)
        pow2[i] = pow2[i-1] << 1;
    
    while(cin >> n >> m && n && m){
        for(int i=1; i<=n; i++)
            if( (m - pow2[i-1]) % pow2[i] ==0 ){
                cout << i << endl;
                break;
            }
    }
    return 0;
}


汉诺塔X——第m次移动的是哪一个盘子,从哪根柱子移到哪根柱子

2511

Problem Description
1,2,…,n表示n个盘子.数字大盘子就大.n个盘子放在第1根柱子上.大盘不能放在小盘上.在第1根柱子上的盘子是a[1],a[2],…,a[n]. a[1]=n,a[2]=n-1,…,a[n]=1.即a[1]是最下面的盘子.
把n个盘子移动到第3根柱子.每次只能移动1个盘子,且大盘不能放在小盘上.
问第m次移动的是哪一个盘子,从哪根柱子移到哪根柱子.
例如:n=3,m=2. 回答是 :2 1 2,即移动的是2号盘,从第1根柱子移动到第2根柱子 。
Input
第1行是整数T,表示有T组数据,下面有T行,每行2个整数n (1 ≤ n ≤ 63) ,m≤ 2^n-1
Output
输出第m次移动的盘子号数和柱子的号数.
Sample Input
4
3 2
4 5
39 183251937942
63 3074457345618258570
Sample Output
2 1 2
1 3 1
2 2 3
2 2 3

顺时针:1 2 3 1
逆时针:1 3 2 1

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<vector>
#include<string>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 65;
typedef long long ll;
ll n,m;
ll pow2[maxn];
int main(){
    //2^n
    pow2[0] = 1;
    for(int i=1; i<maxn; i++)
        pow2[i] = pow2[i-1] << 1;
        
    int T;
    cin >> T;
    while(T--){
        cin >> n >> m;
        
        int i;      //移动的是i号盘子
        for( i=1; i<=n; i++)
            if( (m - pow2[i-1]) % pow2[i] ==0 )
                break;
                    //第k次移动i
        printf("%d ",i);
        
        ll k = m / pow2[i] + 1;
        if(n % 2 == i % 2) {    //逆时针
            if(k%3 == 0)  printf("2 1\n");
            if(k%3 == 1)  printf("1 3\n");
            if(k%3 == 2)  printf("3 2\n");
        } 
        else {                //顺时针
            if(k%3 == 0)  printf("3 1\n");
            if(k%3 == 1)  printf("1 2\n");
            if(k%3 == 2)  printf("2 3\n");
        }
        
    }
    return 0;
}

发布了62 篇原创文章 · 获赞 0 · 访问量 1742

猜你喜欢

转载自blog.csdn.net/jhckii/article/details/104404698
今日推荐