acm之旅--HDU上的汉诺塔问题总结

文章目录

          • 汉诺塔II(1207)
          • 汉诺塔V(1995)
          • 汉诺塔VI(1996)
          • 汉诺塔VII(1997)
          • 汉诺塔III(2064)
          • 汉诺塔IV(2077)
          • 汉诺塔IX(2175)
          • 汉诺塔VIII(2184)
          • 汉诺塔 X(2511)

参考博文 杭电 汉诺塔问题总结
参考博文 HDU汉诺塔系列
标准的汉诺塔问题的递推公式: f[n] = 2 * f[n-1] + 1,且需要2^n-1次。

汉诺塔II(1207)

题目链接汉诺塔II
题目大意:将三个柱子的汉诺塔问题扩展为四个柱子的汉诺塔问题。
Frame算法:F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n)
(1)用4柱汉诺塔算法把A柱上部分的n- r个碟子通过C柱和D柱移到B柱上【F( n- r )步】。

(2)用3柱汉诺塔经典算法把A柱上剩余的r个碟子通过C柱移到D柱上【2^r-1步】。

(3)用4柱汉诺塔算法把B柱上的n-r个碟子通过A柱和C柱移到D柱上【F(n-r)步】。

(4)依据上边规则求出所有r(1≤r≤n)情况下步数f(n),取最小值得最终解。
代码如下:

#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 65;
LL f[MAX+1];

void Hanoi()
{
    LL Min = 1<<29;
    f[1] = 1, f[2] = 3;
    for(int i=3; i<=MAX; i++)
    {
        Min = 1<<29;
        for(int j=1; j<i; j++)
            Min = min(2*f[i-j]+pow(2.0, j)-1, Min*1.0);
        f[i] = Min;
    }
}
int main()
{
    int N;
    Hanoi();
    while(cin >> N)
    {
        cout << f[N] << endl;
    }
    return 0;
}
汉诺塔V(1995)

题目链接汉诺塔V
题目大意:求汉诺塔第k个物块的移动次数。
思路:在演草纸上画一画就能找到规律。若是求移动盘子的总次数,那么有这样的一个关系:f[n] = 2 * f[n-1] + 1;可以看出来,最下面的盘子只需要移动一次,倒数第二个盘子需要移动2次,通过这个式子可以看出递推关系为2倍。即对于N个盘子中的第k个共需要2^(N-k)次移动。
代码如下:

#include 
#include 
#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 61;
LL Pow(int n)
{
    LL s = 1;
    for(int i=0; i<n; i++)
    {
        s *= 2;
    }
    return s;
}
int main()
{
    int N, T, k;
    cin >> T;
    while(T--)
    {
        cin >> N >> k;
        //printf("%lld\n", Pow(N-k));
        cout << Pow(N-k) << endl;
    }
    return 0;
}
汉诺塔VI(1996)

题目链接汉诺塔VI
题目大意:输出汉诺塔问题总共有多少状态。
思路:因为每个盘子可以在三个柱子上的任意一个,所以每个均有3个可能,结果为3^N。
代码如下:

#include 
using namespace std;

typedef long long LL;
LL Pow(int n)
{
    LL s = 1;
    for(int i=0; i<n; i++)
    {
        s *= 3;
    }
    return s;
}
int main()
{
    int T, N;
    cin >> T;
    while(T--)
    {
        cin >> N;
        cout << Pow(N) << endl;
    }
    return 0;
}
汉诺塔VII(1997)

题目链接汉诺塔VII(1997)
题目大意:判断一个汉诺塔的状态是不是最优路径上的状态。
思路:若把n个盘子从柱子a通过柱子b移到柱子c,则先把n-1个盘子从柱子a移动柱子b,再把第n个盘子从a移道c,再把n-1个盘子从b移到a。
所以当判断序列是否符合把n个盘子从a移到c时,第n个只能出现在柱子a的最底部,或柱子c的最底部,否则这个序列错的。
当第n个盘子在a的最底部时,则继续判断剩下的序列是否把n-1个盘子从a移到b。
当第n个盘子在c的最底部时,则继续判断剩下的序列是否把n-1个盘子从b移到c。

代码如下:

#include 
using namespace std;

const int MAX = 65;
int a[MAX], b[MAX], c[MAX];

bool judge(int n, int st[], int ed[], int zh[])
{
    if(zh[0]==n) return 0;
    else if(st[0]==n) return judge(n-1, st+1, zh, ed);
    else if(ed[0]==n) return judge(n-1, zh, ed+1, st);
    return 1;
}
int main()
{
    int T, N, m, p, q;
    cin >> T;
    while(T--)
    {
        cin >> N;
        cin >> m;
        for(int i=0; i<m; i++)
            cin >> a[i];
        cin >> p;
        for(int i=0; i<p; i++)
            cin >> b[i];
        cin >> q;
        for(int i=0; i<q; i++)
            cin >> c[i];
        a[m] = b[p] = c[q] = -1;
        if(judge(N, a, c, b)) cout << "true" << endl;
        else                  cout << "false" << endl;
    }
    return 0;
}
汉诺塔III(2064)

题目链接汉诺塔III
题目大意:在原有的汉诺塔的基础上加上每次只能将盘子由一个柱子移动到相邻的柱子上。
思路:假设有A、B、C的柱子,在移动过程中分为四步。
1>将N-1个盘子从A移动到C(需要F(N-1)次)
2>将第N个盘子从A移动到B(共需要1步)
3>将N-1个盘子从C移动到A(共需要F(N-1)次)
4>将第N个盘子从B移动到C(共需要1步)
5>将N-1个盘子从A移动到C(共需要F(N-1)次)
所以得到递推公式:F(N) = 3*F(N-1)+2,即F(N)=3^N-1
代码如下:

#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 40;
LL f[MAX];
void solve(int N)
{
    f[1] = 2;
    for(int i=2; i<N; i++)
    {
        f[i] = 3*f[i-1]+2;
    }
}
LL F(int N)
{
    if(N==1) return 2;
    return 3*F(N-1)+2;
}

int main()
{
    int N;
    solve(36);
    while(cin >> N)
    {
        cout << f[N] << endl;
        //cout << F(N) << endl;
    }
    return 0;
}
汉诺塔IV(2077)

题目链接汉诺塔IV
题目大意:在汉诺塔III(2064)的基础上加上允许最大的盘子放在小盘子上面。
思路:注意是最大的盘子。移动步骤:

(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))
  设n个盘子的结果为p(n),可得到递推公式:p(n) = 3 * f(n - 2) + 4 , 即p(n) = 3 ^ (n - 1) + 1 (注:这里的f(n)指的是汉诺塔III(2064)的结果)
代码如下:

#include 
#include 
using namespace std;

typedef long long LL;
const int MAX = 40;
LL f[MAX];
void solve(int N)
{
    f[1] = 2;
    for(int i=2; i<N; i++)
    {
        f[i] = 3*f[i-1]+2;
    }
}

int main()
{
    int T, n;
    cin >> T;
    solve(21);
    while(T--)
    {
        cin >> n;
        if(n>2)
            cout << 3*f[n-2]+4 << endl;
        else
            cout << 2*n << endl;
    }
    return 0;
}
汉诺塔IX(2175)

题目链接汉诺塔IX
题目大意:在汉诺塔问题中给出第m次移动的盘子的序号,其中1号为最大的盘子。
思路:第 k+1 次移动s号盘是在第 k ∗ 2 s + 2 s − 1 k*2^s+2^{s-1} k2s+2s1 次,所以如果有满足 m % 2 s = = 2 s − 1 2 ^ s == 2 ^ {s-1} 2s==2s1即说明这次移动的是s号盘。
代码如下:

#include 
#include 
using namespace std;

long long Pow(int m)
{
    long long n = 1.0;
    for(int i=1; i<=m; i++)
    {
        n *= 2.0;
    }
    return n;
}
int main()
{
    long long n, m;
    while(cin >> n >> m && n)
    {
        for(int i=1; i<=n; i++)
        {
            if(m%Pow(i)==Pow(i-1))
            {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}
汉诺塔VIII(2184)

题目链接:汉诺塔VIII
题目大意:计算汉诺塔问题第m步的状态(各柱子的盘子)。
思路:还是先说传统汉诺塔的三步:

(1)先把n-1个盘子从A盘借助C盘移到B盘;

(2)把最大盘n号盘从A盘移动到C盘;

(3)把n-1个盘子从B盘借助A盘移动到C盘。

根据上题的那个结论:第 k+1 次移动s号盘是在第 k ∗ 2 s + 2 s − 1 k*2^s+2^{s-1} k2s+2s1 次,所以n号盘如果移动的话,说明m >= 2 ^ (n-1)。所以对第n号来说有下面两种情况:

  • 如果m >= 2 ^ (n - 1),说明这时最大号盘已经移动,即n号盘此时在C上,此时正在进行的是(3),这时减小规模为n-1,m -= 2 ^ (n-1);

  • 如果m < 2 ^ (n - 1),说明这时最大号盘还未移动,n号盘此时在A上,此时正在进行的是(1),这时减小规模为n-1,m不变;

递归调用该过程,n == 0返回。
代码如下:

#include 
#include 
using namespace std;

typedef long long LL;
vector<int> a, b, c;
LL Pow(int n)
{
    LL s = 1;
    for(int i=1; i<=n; i++) s*= 2;
    return s;
}
void solve(int n, LL m, vector<int> &a, vector<int> &b, vector<int> &c)//a为起始柱子,b为中间的柱子,c为目标柱子,即将第n个由a经b送到c
{
    if(n==0) return;
    LL t = Pow(n-1);
    if(m>=t)
    {
        c.push_back(n);
        solve(n-1, m-t, b, a, c);
    }
    else
    {
        a.push_back(n);
        solve(n-1, m, a, c, b);
    }
}
void Print(vector<int> a)
{
    cout << a.size() << " ";
    for(int i=0; i<a.size(); i++)
    {
        if(i) cout << " ";
        cout << a[i];
    }
    cout << endl;
}
int main()
{
    int T, n;
    LL m;
    cin >> T;
    while(T--)
    {
        a.clear(), b.clear(), c.clear();
        cin >> n >> m;
        solve(n, m, a, b, c);
        Print(a);
        Print(b);
        Print(c);
    }
    return 0;
}
汉诺塔 X(2511)

题目链接汉诺塔 X
题目大意:计算出汉诺塔问题中第m次移动是将几号盘子从哪个柱子移到哪个柱子上。
思路:第k+1次移动s号盘是在第 k ∗ 2 s + 2 s − 1 k*2^s+2^{s-1} k2s+2s1 次。

1–2--3–1叫做顺时针方向,1–3--2–1叫做逆时针方向。

最大盘n号盘只移动一次:1–3,它是逆时针移动。

n-1移动2次:1–2--3,是顺时针移动。

如果n和k奇偶性相同,则s号盘按逆时针移动,否则顺时针。

所以如果 m == k ∗ 2 s + 2 s − 1 k*2^s+2^{s-1} k2s+2s1 ,说明此时是第 k+1 次移动s号盘,这时再根据k+1和n的奇偶性判断移动方向。
 代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define LL __int64
const int maxn = 64;
int main()
{
    int n , T;
    LL m;
    cin >> T;
    while(T--)
    {
        scanf("%d %I64d" , &n , &m);
        LL s , t , l , k;
        s = 1;    t = s << 1;
        for(l = 1 ; l <= n ; l++) {
            if(m % t == s)    break;    //满足 k*(2^l) + 2^(l-1) == m
            s = t;                //s 和 t分别表示 2^(l-1)、2^l
            t <<= 1;
        }
        printf("%d " , l);        //此时移动的是l号盘
        k = m / t + 1;            //第k次移动l号盘
        if(n % 2 == l % 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;
}

你可能感兴趣的:(ACM刷题之路)