<OJ_Sicily>Hanoi_Tower_Sequence

汉诺塔序列求解
本文介绍了一种求解汉诺塔游戏中特定步骤的方法。通过分析汉诺塔序列的规律,给出了快速确定任意位置上盘子编号的算法,并提供了一个C++实现示例。

Description

Hanoi Tower is a famous game invented by the French mathematician Edourard Lucas in 1883. We are given a tower of n disks, initially stacked in decreasing size on one of three pegs. The objective is to transfer the entire tower to one of the other pegs, moving only one disk at a time and never moving a larger one onto a smaller. 

The best way to tackle this problem is well known: We first transfer the n-1 smallest to a different peg (by recursion), then move the largest, and finally transfer the n-1 smallest back onto the largest. For example, Fig 1 shows the steps of moving 3 disks from peg 1 to peg 3.

Now we can get a sequence which consists of the red numbers of Fig 1: 1, 2, 1, 3, 1, 2, 1. The ith element of the sequence means the label of the disk that is moved in the ith step. When n = 4, we get a longer sequence: 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1. Obviously, the larger n is, the longer this sequence will be.

Given an integer p, your task is to find out the pth element of this sequence.

Input

The first line of the input file is T, the number of test cases.

Each test case contains one integer p (1<=p<10^100).

Output

Output the pth element of the sequence in a single line. See the sample for the output format.

Print a blank line between the test cases.

题目解析:这道题主要是求解汉诺塔第p个数的值是多少。现在我们先来看一下汉诺塔的序列关系:

1:1

2:121

3:1213121

4:121312141213121

其实细心可以发现,对于1这个数,都是隔1个元素出现的,对于2这个数,是隔4(也就是2²)个元素,对于3这个数,是隔着8(也就是2³)个元素出现的.....

可以知道对于n这个数,是隔着n^2个元素出现的。对于1这个数,偏移量为1(2^0),对于2这个数,偏移量为2(2^1)......所谓的偏移量就是数字n第一次出现的位置。

因此对于一个数a,它的位置标号可以这样表示: a的位置标号 = a的间隔*n +a的偏移量。其中n表示整数。因此有:

数字偏移量间隔
12^02^1
22^12^2
32^22^3
n2^(n-1)2^n
举个例子,数字4,其所在的位置标号 = 2^3 + 2^4 * k (k=0,1,2...)

因此4这个数的位置标号为:8,24,40...

因此第n个数的位置标号为 2^(n-1)  + k * (2^n)   k = 0, 1, 2, ...

现在的问题是,知道了位置标号m,求对应的数字n是多少。因此有 m = 2^(n-1) + k *2^n

解题思路: 用m除以2,看能够整除多少次,能够整除的次数 + 1就是所求的数。这是因为:

[ 2^(n-1) + k * (2^n)]  / (2^(n-1)) = 1 + 2*k     在这里m除以2 除了(n-1)次,再下一次是(1+2*k)除以2肯定不能整除。

因此所求的数便是m所能整除2的次数 +1

在这道题还需注意的是输入位置标号数的范围,防止溢出,这里使用了处理字符串的方式处理

备注:题目要求每个case之间要有一行空白。因为每次结果输出都要输出一个空白行 cout << endl;


#include <iostream>
#include <map>
using namespace std;
pair<string, int> Mod(string strInt){
    int dividend = 0;
    string result;
    for (int i = 0;  i < strInt.size(); i++) {     // 用于处理长整数
        dividend = dividend* 10 + (strInt[i] - '0');
        int tmp = dividend / 2;
        dividend = dividend %2;
        result = result + (char)(tmp + '0');
    }
    while (1) {
        if (result.size() == 1)break;
        if (result[0] == '0') result.erase(result.begin());
        else break;
    }
    return  make_pair(result,dividend);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int T;
    cin >> T;
    for (int i = 1; i <= T; i++) {
        string data;
        cin >> data;
        cout << "Case " << i << ": " ;
        int count = 0;
        while (1) {
            pair<string, int> result = Mod(data);  // 整除2 ,结果返回商和余数
            data = result.first;
            if (result.second == 0) count ++;     // 余数为0说明可以整除
            else break;
            
        }
        cout <<count + 1 << endl;
        if (i != T) {
            cout << endl;
        }
    }
    return 0;
}




如果要在不使用 `#include <unordered_set>` 的情况下实现类似功能,我们可以使用数组或列表来模拟邻接表,并使用位运算来标记已访问的箱子。这里是一个基于数组的解决方案: ```cpp #include <iostream> #include <vector> using namespace std; const int MAX_N = 100; // 假设箱子不超过100个 // 用一个布尔数组表示箱子是否被访问过,初始化全为false bool visited[MAX_N + 1]; // 定义邻接矩阵,使用二维数组表示箱子之间的钥匙关系 vector<vector<int>> graph(MAX_N + 1, vector<int>(MAX_N + 1)); void findUnlockable(int n, vector<int>& graph, vector<int>& result) { // 初始化遍历标志 for (int i = 0; i <= n; ++i) { visited[i] = false; } // 从第一个箱子开始,进行深度优先搜索 dfs(1, graph, result); } void dfs(int node, vector<int>& graph, vector<int>& result) { // 标记当前箱子已访问 visited[node] = true; // 添加当前箱子到结果中 result.push_back(node); // 遍历邻居节点 for (int nei : graph[node]) { // 如果邻居未被访问,则递归继续搜索 if (!visited[nei]) { dfs(nei, graph, result); } } } int main() { int n, m; cin >> n >> m; // 读取钥匙隐藏关系并更新邻接矩阵 for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; graph[u][v] = 1; // 表示箱子u的钥匙在箱子v中,这里的1只是一个标记 } vector<int> unlockable; // 用于存储结果 findUnlockable(n, graph, unlockable); // 输出结果 for (int box : unlockable) { cout << box << " "; } cout << endl; return 0; } ``` 请注意,这种方法仅适用于箱子数量较小的情况,因为数组大小是固定的,不适合大规模的数据。如果输入规模未知或者可能会很大,建议还是使用 `unordered_set` 或其他哈希集合来优化查找效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值