luogu P9452 [ZSHOI-R1] 河外塔 题解
原题链接:P9452 [ZSHOI-R1] 河外塔
卑微的蒟蒻提供一种部分分思路:二进制基数排序。其实这种思路在比赛时的弱化版稳稳地过了,赛后加强版是拿了 70 分。虽然不是正解,还是求审核大大开恩通过啦!!!
题意
汉诺塔,但是柱子 \(A\) 上的柱子并不像原来那样有从小到大的限制,最终移动到 \(C\) 的过程中也没有任何限制,只要在 \(10^6\) 次内完成即可。
新思路
前置芝士:基数排序,通过各个位数的数码值来进行排序,是很稳定的一个算法,复杂度与最大数位数、数字个数有关。
我们可以把三个柱子都看作三个栈,而且空的柱子有两个,不难想到二进制基数排序。
- 读入,并打擂台出最大数,以通过最大位数计算最终步数。
- 从小位到大位枚举二进制位,是 \(1\) 则移向 \(C\) 柱,是 \(0\) 则移向 \(B\) 柱(哪个柱子其实随意)。然后把 \(C\) 柱上的移回 \(A\) 柱,紧接着把 \(B\) 柱上的移回 \(A\) 柱,使得在这一位上,\(A\) 柱自顶向下一定单调。
- 重复第 2 步,直至最大数的最大位数也被枚举到。不难发现并证明此时 \(A\) 柱上的圆盘从上到下一定是从大到小的。
- \(A\) 柱的圆盘全部弹出至 \(C\),排序完毕。
第 3 步对位排序的操作次数一共是 \(2\times n\times\lfloor\log_{2}n+1\rfloor\),第 4 步回归 \(C\) 柱的操作次数是 \(2\times n - 1\),操作总次数是 \(2\times n-1+2\times n\times\lfloor\log_{2}n+1\rfloor\)。正好在 \(n = 3\times10^4\) 左右被卡。
70 分代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 40004;
inline int Read(); void Rite(int x);
int x = -1,n,d[maxn];
stack<int> a,b,c;
int cnt,cnt1,cnt2;
int main(){
n = Read();
for(int i = 1;i <= n;++i){
d[i] = Read();
x = max(x,d[i]);
//读入并更新最大值
}
for(int i = 1;i <= n;++i)
a.push(d[n - i + 1]);
cout << 2 * n - 1 +
2 * n * int(log2(x) + 1)
<< endl;
for(int k = 0;k <= log2(x);++k){
cnt1 = 0,cnt2 = 0;
for(int i = 1;i <= n;++i){
if((a.top() >> k & 1)){
cout << "A C" << endl; cnt1++;
c.push(a.top()); a.pop();
}//二进制第k位为1
else{
cout << "A B" << endl; cnt2++,
b.push(a.top()); a.pop();
}//二进制第k位为0
}
while(cnt1--){
cout << "C A" << endl,
a.push(c.top()),c.pop();
}
while(cnt2--){
cout << "B A" << endl,
a.push(b.top()),b.pop();
}//回归A柱
}
for(int i = 1;i <= n - 1;++i)
cout << "A B" << endl;
cout << "A C" << endl;
for(int i = 1;i <= n - 1;++i)
cout << "B C" << endl;
return 0;
}
inline int Read(){
char c = getchar();
int x = 0,f = 1;
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}void Rite(int x){
if(x > 9)Rite(x / 10);
putchar(x % 10 + '0');
}

浙公网安备 33010602011771号