社交媒体

九连环问题

[PTA链接 ](https://pintia.cn/problem-sets/2066808699485880320/exam/problems/type/7?problemSetProblemId=2066808699527823367) 这个题目绝对不是目前这个层次的正常学生能做的,本身和汉诺塔问题有点像,难点在于如何分解这个问题为多个子问题,这个题目的条件初见是比较麻烦的,靠直接模拟是不太现实的,暴力的话时间复杂度会不够。所以只能看看找找规律了。 题目本身是有两种情况,全套上和全拆下,本质上这俩一样,只不过是相反的操作而已,这里我们分析全套上这种情况。可以把一个环的操作分为两种,套上和拆下,我们考虑当前环前面的环全部都空了(后面怎样都可以,因为条件没有限制后方),而且操作之后保证这个环之前还是空的。比如我们放 2 的话需要先放 1,为了保证操作之后 2 的前面是空的,所以整个操作就是 1U 2U 1D。 对于 1 而言,套上的话就直接 1U 即可,拆下的话就是 1D,对于 2 的话则是 1U 2U 1D,拆下是 1U 2D 1D,对于 3 的话则是 1U 2U 1D 3U 1U 2D 1D 和 1U 2U 1D 3D 1U 2D 1D,这时候能会发现一个事情,对于套上和拆家这两个操作操纵环的次序是相同的,如:1 2 1 3 1 2 1 和 1 2 1 3 1 2 1。而且对于 3 的套上和拆下本质上都是 2 套上 3U/D 2拆下,这种形式。 根据这个思想我们可以想起来,如果要放上 n 的话,就必须先放上 n - 1,而后的恢复前空还需要把 n - 1 给拆掉,n 的拆掉的话则同理,于是我们可以得到这个公式: n 的套上/拆带掉 = n - 1 的套上 nU/D n - 1的拆掉。这个就是我们的递推公式,根据这个公式写函数就可以得到一个一定正确的操作序列,但是这个序列不一定是最简的。因此我们还需要化简这个序列,观察这个函数求出的序列有很多是 2U 1U 1D 2D 这样的无用操作(即先放上去了,然后马上就拿下来了,这种操作我们肯定是不需要的),于是我们可以使用栈消掉这些无用操作,就可以得到最简操作。 代码如下: ```cpp #include #define first #define second // typedef pair PII; char g[2] = {'U', 'D'}; int n, id, top; char m; struct Node { int a, b; }e[10000]; void get(int x, int f) { if (x <= 0) return ; get(x - 1, 0); if (top && e[top].a == x && e[top].b != f) top -- ; else e[ ++ top].a = x, e[top].b = f; // printf("%d: %c\n", x, g[f]); get(x - 1, 1); } int main() { scanf("%d %c", &n, &m); // printf("%c\n", m); if (m != 'U') { id = 1; for (int i = 1; i <= n; i ++ ) { get(i, id); } } else { while (n) { get(n -- , id); } } for (int i = 1; i <= top; i ++ ) { int a = e[i].a, b = e[i].b; printf("%d: %c\n", a, g[b]); } return 0; } ```