【UOJ#152】【UR#10】汉诺塔

构造

题目大意:

给定三个栈,初始第一个栈中有一个长度为 $n(n\leqslant 10^4)$ 的排列,另外两个栈为空。

一次操作可以把一个栈的栈顶元素移动到另一个栈的栈顶。

要求最多执行 $10^6$ 次操作,使得将所有数移动到同一个栈(任意选择一个),并且满足从栈顶到栈底,数的大小依次递增。

题解:

让你找到一种可行的方案。也算构造题吧。

不难想到我们需要设计一种操作次数为 $O(n\log n)$ 的算法($O(n\sqrt n)$ 也能过但需要非常优秀的常数)。

可以采用归并排序的办法。每次将待排序列划分成两份,对两边分别排序后,再通过线性的时间进行合并。

这里我们对第一个栈的部分排序时,把一半丢到第二个栈里。对第二个栈的部分排序时,把一半丢到第一个栈里。

假设两个栈的栈顶的一部分元素都有序(从上到下依次递增),此时第三个栈保持空闲。

我们每次选择两个栈的栈顶元素较小的那个,把它放到第三个栈中。

这时第三个栈中的元素从上到下是依次减小的。

那么把它依次弹回第一个或第二个栈中,就从小到大排完了。

算法的时间复杂度是 $O(n\log n)$。这个复杂度的算法并不需要太注意常数。

Code:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
vector<int>A[3];
vector<pair<int,int> >op;
int n;
struct info{
    int id,l,r;
};
info solve(info now){
    if(now.l==now.r)return now;
    const int mid=now.l+now.r>>1;
    int beg=A[now.id^1].size();
    while((int)A[now.id].size()-1!=mid){
        op.emplace_back(now.id,now.id^1);
        A[now.id^1].push_back(A[now.id].back());
        A[now.id].pop_back();
    }
    int ed=(int)A[now.id^1].size()-1;
    info L=solve((info){now.id,now.l,mid}),R=solve((info){now.id^1,beg,ed});
    int be2=A[2].size(),top0=L.r,top1=R.r;
    while(top0>=L.l&&top1>=R.l)
    if(A[now.id][top0]<A[now.id^1][top1]){
        A[2].push_back(A[now.id][top0]);
        A[now.id].pop_back();
        op.emplace_back(now.id,2);
        --top0;
    }else{
        A[2].push_back(A[now.id^1][top1]);
        A[now.id^1].pop_back();
        op.emplace_back(now.id^1,2);
        --top1;
    }
    while(top0>=L.l){
        A[2].push_back(A[now.id][top0]);
        A[now.id].pop_back();
        op.emplace_back(now.id,2);
        --top0;
    }
    while(top1>=R.l){
        A[2].push_back(A[now.id^1][top1]);
        A[now.id^1].pop_back();
        op.emplace_back(now.id^1,2);
        --top1;
    }
    while((int)A[2].size()-1>=be2){
        A[now.id].push_back(A[2].back());
        op.emplace_back(2,now.id);
        A[2].pop_back();
    }
    return now;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;++i){
        int x;
        cin>>x;
        A[0].push_back(x);
    }
    reverse(A[0].begin(),A[0].end());
    solve((info){0,0,n-1});
    cout<<op.size()<<'\n';
    for(auto it:op)
    cout<<it.first+1<<' '<<it.second+1<<'\n';
    return 0;
}