CSES 2402:Two Stacks Sorting

CSES 2402Two Stacks Sorting


題目大意:給你一個 $1\sim n$ 的排序 $a_1\sim a_n$,你有兩個 $\text{stack}$,你可以將 $a_i\ \text{push}$ 到其中一個 $\text{stack}$ 上面,或是將其中一個 $\text{stack}$ 頂端的元素 $\text{pop}$ 掉,加入到答案的尾端,最後想要答案為 $1\sim n$,請問是否可行,若可以,第 $i$ 個數字請輸出 $a_i$ 被 $\text{push}$ 到哪個 $\text{stack}$。

解法:如果只有一個 $\text{stack}$ 的情況,怎樣才不能成功呢?可以知道若 $i<j<k, a_k < a_i < a_j$ 就無法成功,且若無法成功,一定存在一組 $i<j<k, a_k < a_i < a_j$。首先如果有 $a_k < a_i < a_j$,因為 $a_k$ 比較小,在 $\text{pop}$ 之前 $a_i, a_j$ 都不能被 $\text{pop}$,可是 $a_i < a_j$,代表 $a_i$ 要在 $a_j$ 被加入之前就 $\text{pop}$ 掉,於是會矛盾;而如果不存在 $a_k < a_i < a_j$,就一定能成功,因為當 $a_j$ 要被加入時,所有 $<a_i$ 的數字在 $a_j$ 之後都不會出現,代表 $a_i$ 在 $a_j$ 進入之前就被 $\text{pop}$ 掉了,$a_j$ 就可以進入 $\text{stack}$。

先考慮一個 $O(n^2)$ 的作法:枚舉 $i, j(i<j, a_i < a_j)$,看 $a_j$ 之後有沒有數字 $<a_i$,若有,代表 $a_i, a_j$ 不能在同一個 $\text{stack}$,那就在 $a_i, a_j$ 間建一條邊,之後可以二分圖著色,若可以著色,代表有解,否則不行。可以令 $mn_i$ 代表 $a_i\sim a_n$ 的最小值,這樣枚舉加上著色的複雜度就是 $O(n^2)$。著色是其中一種作法,但是因為最多可能加 $O(n^2)$ 條邊,我們現在用另外一種並查集的作法,就是如果 $a_i, a_j$ 不能在同一個 $\text{stack}$,那就把 $a_i, \text{not}\ a_j$ 所在的集合合併,把 $a_j, \text{not}\ a_i$ 所在的集合合併,之後若 $a_i, \text{not}\ a_i$ 在同一個集合,那就代表不行。

現在我們要來優化複雜度,我們可以令 $L = mn_{i + 1} + 1, R = a_i - 1$,那輪到 $a_i$ 時,事實上就是把範圍在 $L\sim R$,且之前出現過的數字做並查集的操作,且可以知道最後會把 $L\sim R$ 的所有集合變成一個集合,這樣總複雜度就會是 $O(n\times\log(n))$。

$\text{Code:}$

#pragma GCC optimize("O3")
#pragma GCC target("popcnt")
#include <bits/stdc++.h>
using namespace std;

#define Waimai ios::sync_with_stdio(false),cin.tie(0)
#define FOR(x,a,b) for(int x=a;x<=b;x++)
#define pb emplace_back
#define F first
#define S second

const int SIZE = 2e5 + 5;
const int INF = 2e9;

int n;
int a[SIZE], mn[SIZE], ans[2 * SIZE];
int to[2 * SIZE], h[2 * SIZE];
set<int> num;
set<tuple<int, int, int>> s;

int dsu (int x) {
    return x == to[x] ? x : (to[x] = dsu (to[x]));
}

void Merge (int a, int b) {
    a = dsu (a), b = dsu (b);
    if (a == b) return;
    if (h[a] < h[b]) swap (a, b);
    to[b] = a;
    h[a] += h[a] == h[b];
}

int opp (int x) {
    return x <= n ? x + n : x - n;
}

void solve() {
    cin >> n;
    FOR (i, 1, n) cin >> a[i];
    iota (to, to + 2 * n + 1, 0);
    mn[n + 1] = INF;
    for (int i = n; i >= 1; i--) mn[i] = min (mn[i + 1], a[i]);
    FOR (i, 1, n) {
        int L = mn[i + 1] + 1, R = a[i] - 1;
        vector<tuple<int, int, int>> tmp;
        int ll = INF, rr = 0, gg;
        if (L <= R) {
            for (auto it = s.lower_bound ({L, 0, 0}); it != s.end(); it++) {
                auto [r, l, g] = *it;
                if (l > R) break;
                auto lit = num.lower_bound (max (L, l));
                if (lit == num.end() || *lit > min (R, r)) break;
                Merge (g, opp (a[i]));
                Merge (opp (g), a[i]);
                tmp.pb (*it);
                ll = min (ll, l);
                rr = max (rr, r);
                gg = g;
            }
        }
        if (tmp.size()) {
            for (auto t : tmp) s.erase (t);
            s.insert ({rr, ll, dsu (gg)});
        }
        auto it = s.lower_bound ({a[i], 0, 0});
        if (it == s.end() || get<1> (*it) > a[i]) s.insert ({a[i], a[i], a[i]});
        else {
            auto [r, l, g] = *it;
            s.erase (it);
            if (l < a[i]) {
                auto it = prev (num.lower_bound (a[i]));
                s.insert ({*it, l, g});
            }
            s.insert ({a[i], a[i], a[i]});
            if (r > a[i]) {
                auto it = num.upper_bound (a[i]);
                s.insert ({r, *it, g});
            }
        }
        num.insert (a[i]);
    }
    FOR (i, 1, n) if (dsu (i) == dsu (i + n)) {
        cout << "IMPOSSIBLE\n";
        return;
    }
    FOR (i, 1, n) {
        if (!ans[dsu (a[i])]) {
            int k = ans[dsu (a[i] + n)];
            if (k != 1) ans[dsu (a[i])] = 1;
            else ans[dsu (a[i])] = 2;
            ans[dsu (a[i] + n)] = 3 - ans[dsu (a[i])];
        }
        cout << ans[dsu (a[i])] << " \n"[i == n];
    }
}

int main() {
    Waimai;
    solve();
}

我的分享就到這裡結束了,如果喜歡我的 $\text{Blog}$,歡迎追蹤!

留言

熱門文章