[NOIP2008 提高组] 双栈排序 简要题解

[NOIP2008 提高组] 双栈排序 题解

注:本题解展现了较为简略的思路,若需查阅完整思路,可以查看作者博文 https://blog.csdn.net/hexu2010/article/details/139346448 获取完整思路,也请点赞、关注、收藏支持。

题目描述

Tom 最近在研究一个有趣的排序问题。如图所示,通过 2 2 2 个栈 S 1 S_1 S1 S 2 S_2 S2,Tom 希望借助以下 4 4 4 种操作实现将输入序列升序排序。

  • 操作 a \verb!a! a:将第一个元素压入栈 S 1 S_1 S1
  • 操作 b \verb!b! b:将 S 1 S_1 S1 栈顶元素弹出至输出序列。
  • 操作 c \verb!c! c:将第一个元素压入栈 S 2 S_2 S2
  • 操作 d \verb!d! d:将 S 2 S_2 S2 栈顶元素弹出至输出序列。

Tom 希望知道其中字典序最小的操作序列是什么,若无合法方案则输出 0。

简要题解

完整题解请见作者博文 https://blog.csdn.net/hexu2010/article/details/139346448

手动模拟可以发现如下性质:

对于任意的 i < j < k i<j<k i<j<k,若 A k < A i < A j A_k<A_i<A_j Ak<Ai<Aj,则 A i , A j A_i,A_j Ai,Aj 不能同时处于一个栈中。

把不能处于同一个栈中的点对 ( A i , A j ) (A_i,A_j) (Ai,Aj) 连边,在产生的图中进行二分图判定即可。如果所得的图不是二分图,就说明原序列不能用双栈排序解决。(经本人测试,完成本部分代码可以获得 10pts 的高分)

接下来考虑如何求得方案。判定是否为二分图时,dfs 的过程中就已经得出了每个元素应被划分为二分图的哪一个部分。按照每个点所处的部分对应元素应进入哪一个栈,可以构造出字典序并非最小的方案。(经本人测试,直接输出方案就可以通过全部官方数据,但各大 OJ 基本已设置了 hack 数据)

接下来考虑优化已经求得的压栈/弹栈方案,使其字典序更小。由于只有 ( a , d ) (a,d) (a,d) ( b , c ) (b,c) (b,c) 的相对顺序可以改变,就规定 ad 属于同一种字符,bc 属于同一种字符,将已经求得的答案分为不同的段,每段之内排序即可。

例如 acabbabdab 分段为 a/c/a/bb/a/da/b,段内排序后变为 a/c/a/bb/a/ad/b,也就是 acabbaadb,获得了一个字典序更小的方案。

虽暂时无法证明这是最优的,但是足以通过 OJ 的测试数据。(如果有证法欢迎补充)

参考代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    for(; ch < '0' || ch > '9'; w *= ch == '-' ? -1 : 1, ch = getchar());
    for(; ch >= '0' && ch <= '9'; s = 10 * s + ch - '0', ch = getchar());
    return s * w;
}
const int MAXN = 2005;
const int MAXM = 500005;
struct Graph{
    struct Edge{
        int to, nxt;
    } e[MAXM << 1];
    int head[MAXN], tot;
    void add(int u, int v){
        e[++tot].to = v;
        e[tot].nxt = head[u];
        head[u] = tot;
    }
} G;
int N, a[MAXN], sufmin[MAXN], co[MAXN], op[MAXN << 1], res[MAXN], Index;
bool judge(int u, int fa, int color){
    co[u] = color;
    for(int i = G.head[u], v; i; i = G.e[i].nxt){
        v = G.e[i].to;
        if(v == fa) continue;
        if(co[v] == co[u]) return false;
        if(judge(v, u, -color) == false) return false;
    }
    return true;
}
void work(int l, int r){
    int cnt1 = 0, cnt2 = 0, bas = min(res[l], 5 - res[l]);
    for(int i = l; i <= r; i++){
        if(res[i] * 2 < 5) cnt1++;
        else cnt2++;
    }
    for(int i = l; i <= r; i++){
        if(i <= l + cnt1 - 1) res[i] = bas;
        else res[i] = 5 - bas;
    }
}
signed main(){
    N = read();
    for(int i = 1; i <= N; i++) a[i] = read();
    sufmin[N] = a[N];
    for(int i = N - 1; i >= 1; i--) sufmin[i] = min(sufmin[i + 1], a[i]);
    for(int i = 1; i <= N; i++){
        for(int j = i + 1; j <= N; j++){
            if(sufmin[j] < a[i] && a[i] < a[j]){
                G.add(i, j), G.add(j, i);
            }
        }
    }
    for(int i = 1; i <= N; i++){
        if(co[i]) continue;
        if(judge(i, i, 1) == false){
            cout << 0 << endl;
            return 0;
        }
    }
    stack<int> st1, st2;
    int cur = 1;
    for(int i = 1; i <= N; i++){
        if(co[i] > 0) st1.push(a[i]), res[++Index] = 1;
        else st2.push(a[i]), res[++Index] = 3;
        while((!st1.empty() && st1.top() == cur) || (!st2.empty() && st2.top() == cur)) {
            if(!st1.empty() && st1.top() == cur){
                st1.pop(), res[++Index] = 2;
            } else {
                st2.pop(), res[++Index] = 4;
            }
            cur++;
        }
    }
    while((!st1.empty() && st1.top() == cur) || (!st2.empty() && st2.top() == cur)) {
        if(!st1.empty() && st1.top() == cur){
            st1.pop(), res[++Index] = 2;
        } else {
            st2.pop(), res[++Index] = 4;
        }
        cur++;
    }
    int lst = -1, lstpos = 0;
    for(int i = 1; i <= Index; i++){
        if(min(res[i], 5 - res[i]) != lst){
            work(lstpos, i - 1);
            lstpos = i;
        }
        lst = min(res[i], 5 - res[i]);
    }
    for(int i = 1; i <= Index; i++){
        cout << (char)(res[i] - 1 + 'a') << " ";
    }
    cout << endl;
    return 0;
}
  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值