[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) 的相对顺序可以改变,就规定 a
和 d
属于同一种字符,b
和 c
属于同一种字符,将已经求得的答案分为不同的段,每段之内排序即可。
例如 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;
}