[NOIp2008] 雙棧排序 (二分圖染色 + 貪心)
題意
給你一個長為 \(n\) 的序列 \(p\) ,問是否能夠通過對於兩個棧進行 push, pop(print)
操作使得最後輸出序列單調遞增(即為 \(1 \cdots n\) ),如果無解輸出 \(0\) 。
每個操作有個優先順序,push(1) > pop(1) > push(2) > pop(2)
,輸出優先順序最大的一組解。
\(n \le 1000\)
題解
有興趣可以來逛逛 我的部落格。
洛谷前面大部分題解,對於後面直接模擬的思路肯定是錯的,本文介紹一個基於貪心的演算法(不知道對不對,因為沒有強資料驗證)。
首先考慮只有一個棧的時候如何解決這個問題。
就是對於一對位置 \((i, j)\)
我們預處理 \(\displaystyle f_i = \min_{j = i}^{n} p_j\) ,就可以在 \(O(n ^ 2)\) 的時間內判斷一對 \(i, j\) 是否可以共存了(也就是 \(f_{j + 1} < p_i < p_j\) )
然後對於存在兩個棧的情況,我們就需要把 \(p\)
這樣的話,我們對於一對不能共存的 \(i, j\) 連邊,然後進行二分圖染色。如果不可染,那麼就是不存在一組合法解。
之後我們只需要解決使得最後解字典序最小的限制。
我們染色的時候 BFS
染色,儘量把在前面的放入第一個棧。
然後後面得到操作序列直接模擬肯定是個錯的。
舉個樣例:
5 2 4 1 3 5
標準輸出:
a c a b b a b a d b
前面大部分錯誤的輸出:
a c a b b a b d a b
為什麼呢,因為你向第二個棧 push
後,不一定現在拿出來 pop
,第一個棧中能繼續 push
。
那麼我們就貪心一下,我們在 push
pop
,等到需要 pop
的時候再 pop
。
哪些時候需要 pop
呢,就是這個棧不合法的時候需要 pop
(也就是這個棧 棧頂到棧底 不單調遞增的時候,不滿足單調棧性質)
但是注意向第二個棧中 push
之前,因為第一個棧的 pop
優先順序更高,我們看能不能先 pop
第一個棧。
這樣就應該是最優的了,注意最後要把兩個棧按順序清空。
程式碼
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define pb push_back
using namespace std;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("P1155.in", "r", stdin);
freopen ("P1155.out", "w", stdout);
#endif
}
const int N = 1010, inf = 0x7f7f7f7f;
int n, P[N], minv[N], col[N];
int pos = 1;
stack<int> S[2];
inline void out(char ch) {
putchar (ch); putchar (' ');
}
inline bool Pop(int id) {
if (!S[id].empty() && S[id].top() == pos) {
out(id ? 'd' : 'b'), S[id].pop(), ++ pos;
return true;
}
return false;
}
inline void Push(int cur, int id) {
if (id == 1) { while(Pop(0)); }
while (!S[id].empty() && S[id].top() < cur)
if (!Pop(id)) Pop(id ^ 1);
if (id == 1) { while(Pop(0)); }
S[id].push(cur); out(id ? 'c' : 'a');
}
vector<int> G[N];
int main () {
File();
n = read();
For (i, 1, n)
P[i] = read();
minv[n + 1] = n + 1;
Fordown (i, n, 1)
minv[i] = min(minv[i + 1], P[i]);
For (i, 1, n) For (j, i + 1, n)
if (minv[j + 1] < P[i] && P[i] < P[j])
G[i].pb(j), G[j].pb(i), col[i] = col[j] = -1;
For (i, 1, n) if (!~col[i]) {
queue<int> Q; Q.push(i); col[i] = 0;
while (!Q.empty()) {
int u = Q.front(); Q.pop();
for (int v : G[u]) {
if (~col[v] && col[v] != (col[u] ^ 1)) return puts("0"), 0;
if (!~col[v]) Q.push(v);
col[v] = col[u] ^ 1;
}
}
}
For (i, 1, n)
Push(P[i], col[i]);
bool flag = true;
while (flag) {
flag = false;
while(Pop(0)) flag = true;
while(Pop(1)) flag = true;
}
return 0;
}