问题 B: Moving Buildings(hanoi)

问题 B: Moving Buildings
时间限制: 1 Sec 内存限制: 128 MB
题目编号 10277
题目描述
Consider two identical modular apartment buildings (albeit different colors) as shown below:
在这里插入图片描述
Each building is situated on a single lot (the white building is on Lot 1 and the black building is on Lot 3). Since the buildings are modular, each floor is built separately and stacked on the previous floor.
The size of each floor always gets smaller as the building gets taller, since a smaller floor is unable to properly support a bigger floor.

The owners of the buildings chose the same builder to construct each building, however, the builder was confused and got the colors swapped. The owner of Lot 1 wanted the building to be black, and the owner of Lot 3 wanted the building to be white. The builder has to switch the buildings around,
but the problem is, there is only one crane available to lift and move the building floors. It is handy, though, that there are vacant lots next to each building (Lots 2 and 4). The owners of Lots 2 and 4 have agreed to allow the building to use the empty lots as a temporary storage place during the
building swap with restrictions. The owner of Lot 1 and the owner of Lot 4 do not get along and the owner of Lot 4 will not allow any of the white building to be placed on Lot 4. Similarly, the owner of lot 2 and the owner of lot 3 do not get along and the owner of Lot 2 will not allow any of the black building to be placed on Lot 2.
在这里插入图片描述
You will write a program to figure out the minimum number of floor moves needed to completely swap the buildings on Lots 1 and 3, given that no white floor may ever appear on Lot 4 and no black floor many ever appear on Lot 2, and, a floor may only be placed on the ground or on a strictly larger floor.
The first move will always be the topmost white floor from Lot 1 to Lot 2. The picture below is the final result AFTER the swap.

输入
The first line of input contains a single decimal integer P, (1≤P≤100), which is the number of data sets that follow. Each data set should be processed identically and independently.

Each data set consists of 1 line of input. The line contains the data set number, K, followed by the integer number of floors in the building, N, (1≤N ≤25), followed by a positive 32-bit integer S, indicating the desired move number.

输出
For each data set there is a single line of output.

The output line consists of the data set number, K, followed by the minimum number of moves needed to swap the buildings, followed by the instructions for move S in the form:
MOVE color FLOOR FROM LOT f TO LOT t, where color is either white or black, f is the starting lot number (1-4) and t is the destination lot number (1-4).

样例输入
4
1 5 45
2 5 46
3 25 654321
4 25 654320
样例输出
1 93 MOVE white FLOOR FROM LOT 3 to LOT 2
2 93 MOVE white FLOOR FROM LOT 1 to LOT 2
3 100663293 MOVE white FLOOR FROM LOT 3 to LOT 2
4 100663293 MOVE white FLOOR FROM LOT 3 to LOT 1

题目大意
现有1 2 3 4四块空地,1上有白色大楼,3上有黑色大楼(类似汉诺塔那种),现在需要互换两座楼的位置,其中每个楼层只能放在严格比他大的块上,白块不能放在4上,黑块不能放在2上,求最少移动次数以及第s步的操作(需要推导出楼层颜色与初末位置)
题目规定第一步移动固定为将一个白块从1移动到2。

做个准备:汉诺塔问题
熟悉汉诺塔可以跳过这里,直接从下面的代码以后开始看
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子A、B、C,,在一根柱子A座上有64个黄金圆盘(如图所示),盘子大小不等,大的在上,小的在下。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放从A座移到在另一根柱子C座上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。且在移动过程中在3个座上始终保持大盘在下,小盘在上。在移动地程中可以行用B座柱子,要求编程序打印出移动的步骤。
在这里插入图片描述
【输入】
一个整数n,表示有n个盘子。
【输出】
具体的移盘步骤。

方法:
我最先看到这个题的时候直接懵了(当时是预置了main函数只需要写一个hanoi函数),一个函数输出整个移动步骤…但是不能放弃,没思路的时候就写几个找思路,然后就发现将n个盘子从a到c可以化为将n-1个盘子从a到b,再将最大的盘子从a移到c,再将n-1个盘子移动到c,其中第一步和第三步都可以通过递归调用自己解决,第二步只有一行输出,因此可以写一个递归函数完成汉诺塔问题。
同时,可求得将n个圆盘的塔进行移动所需要的步数是 2 n − 1 2^n-1 2n1
附一个汉诺塔的代码

#include <stdio.h>
void hanoi(int n,char a,char b,char c)  
{  
    if (n!=1)  
    {  
            hanoi(n-1,a,c,b);  //将n-1个圆盘组成的汉诺塔从a移动到b
            printf("%c--->%c\n",a,c);  //将最底下的圆盘移动到c
            hanoi(n-1,b,a,c);  //再从b到c
    }  
    else printf("%c--->%c\n",a,c); //n==1,仅需一步 
}  
int  main()
{
    int n;
    scanf("%d", &n);
    hanoi(n, 'A', 'B', 'C');
    return 0;
}

本题思路
类似研究汉诺塔的方法,先写几个数量小的推一推…
首先是显而易见的1个:
1->2 W
3->1 B
2->3 W
以及2个:
1->2 W
3->1 B
3->4 B
1->4 B
1->3 W
4->3 B
4->1 B
3->1 B
2->3 W
其中,第二步到第四步等同于把2层的汉诺塔由3经1移动到4的过程,第六步到第八步为从4经3移动到1的过程
因此,将汉诺塔的移动后面加上层数,可写为:
1->2 W
3->4 B (2)
1->3 W
4->1 B (2)
2->3 W

再比如题目样例中提到的5个的情况:(箭头简记为‘-’)
1-2 W
3-4 B (2)
1-3 W
2-3 W
1-2 W
3-2 W (2)
3-1 B
4-1 B (2)
3-4 B
1-4 B (3)
1-3 W
2-3 W (3)
1-2 W
3-2 W (4)
3-1 B
2-1 W (4)
2-3 W
1-2 W (3)
1-3 W
4-3 B (3)
4-1 B
3-4 B (2)
3-1 B
2-1 W (2)
2-3 W
1-2 W (1)
1-3 W
4-1 B (2)
2-3 W
可以算出,这个方案的移动步数为93次,与样例给出的步数相同。
再分析这个移动方法的规则:先移动白块第一块,然后借助白色第二块移动黑色一二块,再借助黑色第三块移动白色二三块,以此类推…每次移动两块的原因是:1.每次最多移动两块,否则将被题目限制:需要大块的另一种颜色的楼层做底座。2.每次移动两块比移动一块所需步数更少,因为尽可能地减少了上方塔的重复移动次数。例如:如果2层塔的第二步是3-4的话要移动大块还需要再次4-1(或者2-3,等同于4-1),也就意味着3-4这一步是多余的操作,浪费了移动次数。

因此,假设塔有奇数层,我们需要将黑塔的n-1层移到4,将白塔的n层移到2,再将黑塔底座移动到1,再将黑塔的n-1层移到1,将白塔的n层移到3,若将黑塔的三个移动步骤合并,即为黑塔整体移到1,白塔整体先移到2再移到3,也就是三次汉诺塔所需的步骤,只不过这个步骤是交替进行的。如果塔是偶数层,则需要移动两次黑塔和一次白塔,仍然符合三次汉诺塔,因此移动的次数为 3 ∗ 2 n 3*2^n 32n步。

接下来我们还要寻找第s步的移动,所以我们需要寻找交替进行的规律,这个规律我是从5层塔中寻找的:
首先,寻找每次移动同颜色块的个数:我们将这93步从中间的3-1 B(第47步)进行分隔,单看塔的颜色,前半部分的移动方案为:白塔1次,黑塔3次,白塔6次,黑塔12次,白塔24次。恰好五次移动,每次移动除第一次外,符合 3 ∗ ( 2 n − 2 − 1 ) 3*(2^{n-2}-1) 3(2n21),移动塔的颜色黑白交替;后半部分的移动方案刚好是前半部分移动方案的倒序。

接下来对每次移动同颜色块进行分析:
将第三次移动和第五次移动进行比对:

第三次第五次
1-3 W1-3 W
2-3 W (1)2-3 W (3)
1-2 W1-2 W
3-2 W (2)3-2 W (4)

为更明显找到此规律,我把第三次第二行的后面加上1。
这不是偶然的,他们分别描述了如下的操作:将1中一层底盘移动到3,将2的塔移动到3,将1中底盘移动到2,将3的塔移动到2,这四步操作即为将1的两个底盘移动到2的操作。
因此我们可将第n次的移动进行如下描述:(n小于总移动次数的一半)
1-3 W
2-3 W(n-2)
1-2 W
3-2 W (n-1)
同样地,将第二次移动拆分后可以整理出偶数次的规律

第二次第四次
3-1 B3-1 B
4-1 B (2)
3-4 B3-4 B
1-4 B1-4 B (3)

在不影响操作的情况下,我继续进行了变形:

第二次第四次
3-1 B3-1 B
4-1 B (0)4-1 B (2)
3-4 B3-4 B
1-4 B (1)1-4 B (3)

其中,0层汉诺塔表示不进行任何操作

因此可得出偶数次的移动规律为:
3-1 B
4-1 B (n-2)
3-4 B
1-4 B (n-1)

当s恰好为 3 ∗ 2 n − 1 + 1 3*2^{n-1}+1 32n1+1时,即为将底盘移动的步骤,若n为奇数,将黑盘从3移动到1,反之将白盘从1移动到3。
当s大于 3 ∗ 2 n − 1 + 1 3*2^{n-1}+1 32n1+1时,由于移动的后半部分也有移动策略,则可以用类似方法求解:
倒数的奇数次规律:
2-1 W (n-1)
2-3 W
1-2 W (n-2)
1-3 W
倒数的偶数次规律:
4-3 B (n-1)
4-1 B
3-4 B (n-2)
3-1 B
在代码实现的过程中,我将这里进行了反转,保持了移动次数和前面规律的统一(移动次数按照顺序分别为1次, 2 n − 2 − 1 2^{n-2}-1 2n21次,1次, 2 n − 1 − 1 2^{n-1}-1 2n11次)

下面对这些规律进行代码实现。

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=25+5;
ll pow2[N];
string str[2]={"white","black"};
void hanoi(int a,int b,int c,int p,int k,int &f,int &t)
{
    //函数功能:求汉诺塔中的第p步移动
    //a,b,c分别为塔的初始位置,末位置和途径的格子。
    //k为当前汉诺塔所需的移动步数,
    //f、t为传回的数据,分别代表本次移动的初末位置。
    int mid=k/2;
    if(p==mid+1)
    {
        f=a;
        t=b;
        return;
    }
    if(p>mid)
    {
        hanoi(c,b,a,p-mid-1,mid,f,t);
    }
    else
        hanoi(a,c,b,p,mid,f,t);
}
int main()
{
    pow2[0]=1;
    for(int i=1;i<N;i++)
        pow2[i]=2*pow2[i-1];//2的幂次,之后会用
    int T;
    cin>>T;
//    int z=1;
    while(T--)
    {
        int id,n,s;
        cin>>id>>n>>s;
//        id=0,n=2,s=z;
//        z++;
        ll ans=(pow2[n]-1)*3;
        cout<<id<<' '<<ans<<' ';
        int f,t,flag=1,kk=0;
        if(s==ans/2+1)//恰好为中间次操作,移动底盘
        {
            if(n%2==1)
                cout<<"MOVE black FLOOR FROM LOT 3 to LOT 1"<<endl;
            else
                cout<<"MOVE white FLOOR FROM LOT 1 to LOT 3"<<endl;
            continue;
        }
        if(s>ans/2+1)//若超过一半,则为后半部分的移动,将s反转并令kk=1进行标记。
        {
            s=ans+1-s;
            kk=1;
        }
        if(s==1)//第一步移动,即开始或结束时的步骤
        {
            if(kk==0)
            {
                cout<<"MOVE white FLOOR FROM LOT 1 to LOT 2"<<endl;
                continue;
            }
            else
            {
                cout<<"MOVE white FLOOR FROM LOT 2 to LOT 3"<<endl;
                continue;
            }
        }
        s--;//减掉第一次移动
        int q=3;
        for(int j=2;j<=n;j++,q*=2,flag=!flag)
        {
        	//j为第j大次移动,q为本次移动的步数,flag代表当前移动颜色,1为黑0为白
            if(s<=q)
            {
                if(s==1)//循环内的第一步,正序倒序恰好相同(应该是巧合吧),至于flag有关
                {
                    if(flag)
                        cout<<"MOVE black FLOOR FROM LOT 3 to LOT 1"<<endl;
                    else
                        cout<<"MOVE white FLOOR FROM LOT 1 to LOT 3"<<endl;
                    break;
                }
                else s--;
                if(s<=pow2[j-2]-1)//循环内的第二步,从j-2层汉诺塔的移动步骤中寻找答案
                {
                    if(flag)
                    {
                        if(kk==0)
                        {
                            hanoi(4,1,3,s,pow2[j-2]-1,f,t);
                        }
                        else
                        {
                            hanoi(3,4,1,pow2[j-2]-s,pow2[j-2]-1,f,t);
                            //注意这里第四个参数需要进行反转,因为现在的s是倒数的步骤数,hanoi求的是正数p步的答案
                        }
                    }
                    else
                    {
                        if(kk==0)
                        {
                            hanoi(2,3,1,s,pow2[j-2]-1,f,t);
                        }
                        else
                        {
                            hanoi(1,2,3,pow2[j-2]-s,pow2[j-2]-1,f,t);
                        }
                    }
                    cout<<"MOVE "<<str[flag]<<" FLOOR FROM LOT "<<f<<" to LOT "<<t<<endl;
                    //输出,str字符串数组存有黑白两种颜色
                    break;
                }
                else s-=pow2[j-2]-1;
                if(s==1)//第三次移动一步,注意分四种情况讨论
                {
                    if(kk==0)
                    {
                        if(flag)
                            cout<<"MOVE black FLOOR FROM LOT 3 to LOT 4"<<endl;
                        else
                            cout<<"MOVE white FLOOR FROM LOT 1 to LOT 2"<<endl;
                    }
                    else
                    {
                        if(flag)
                            cout<<"MOVE black FLOOR FROM LOT 4 to LOT 1"<<endl;
                        else
                            cout<<"MOVE white FLOOR FROM LOT 2 to LOT 3"<<endl;
                    }
                    break;
                }
                else s--;
                if(s<=pow2[j-1]-1)//第四次移动,和第二次移动类似
                {
                    if(flag)
                    {
                        if(kk==0)
                        {
                            hanoi(1,4,3,s,pow2[j-1]-1,f,t);
                        }
                        else
                        {
                            hanoi(4,3,1,pow2[j-1]-s,pow2[j-1]-1,f,t);
                        }
                    }
                    else
                    {
                        if(kk==0)
                        {
                            hanoi(3,2,1,s,pow2[j-1]-1,f,t);
                        }
                        else
                        {
                            hanoi(2,1,3,pow2[j-1]-s,pow2[j-1]-1,f,t);
                        }
                    }
                    cout<<"MOVE "<<str[flag]<<" FLOOR FROM LOT "<<f<<" to LOT "<<t<<endl;
                break;
                }
            }
            else s-=q;//步骤数大于本次移动使用的步骤数,减去并继续循环
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值