题解 ARC165F【Make Adjacent】

区间排序问题,主席树优化建图,最小字典序拓扑排序(priority_queue)

problem

给定一个长度为 \(n*2\) 的序列,其中每种元素恰好出现了 2 次。

允许每次选择任意两个相邻的元素交换。
那么必定存在一个最小 \(k\):使得 \(k\) 次交换以后所有相同的元素都是相邻的。

问恰好操作 \(k\) 次后,所有满足(相同的元素全都相邻)的方案中,最小字典序的一个方案。\(n\leq 2\times 10^5\)

solution

考虑这个是所谓的区间排序问题,所以我们考虑三种情况:区间有交不包含,区间包含,区间不交。

只要我们考虑了所有的区间对,所有的区间对都不交,那么就达到了最终状态,不妨考虑一个区间对:

有交不包含

形如 ([)] 的东西,考虑说,如果在最终排列中 ()[] 前面,然后考察它们的逆序对,这样的话有一个逆序对。如果在最终排列中 []() 前面,这样有三个逆序对,这样的最终排列一定不优,所以我们得出结论 ()[] 前面。

包含

形如 ([]) 的东西,你发现无论考虑 ()[] 的关系如何,逆序对个数都是 \(2\),于是他们的顺序无所谓。

无交

形如 ()[] 的东西,明显 ()[] 的前面是必须的,顺序不能调换。

所以有了这些结论我们就可以建出拓扑图进行最小字典序拓扑排序了。然后 \(n=2\times 10^5\) 的话,用主席树优化建图或者直接分治(分治相对好写一点)就行。拓扑排序将点分成关键点和非关键点,每次优先选非关键点,选关键点的时候选最小的。这样复杂度就对了(非关键点不要 priority_queue)

\(O(n\log n)\)

code

点击查看代码

#include <numeric>
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
int n, L[200010], R[200010], id[200010];
int tot = 0;
vector<int> g[200010 << 6];
int deg[200010 << 6];
void add(int u, int v) {
    if (v) g[v].push_back(u), deg[u] += 1;
}
void solve(int l, int r) {
    if (l == r) return ;
    int mid = (l + r) >> 1;
    solve(l, mid), solve(mid + 1, r);
    for (int i = l; i <= mid; i++) id[i] = -id[i];
    sort(id + l, id + r + 1, [&](int i, int j) { return R[abs(i)] < R[abs(j)];});
    int pre = 0;
    for (int i = l; i <= r; i++) {
        if (id[i] > 0) add(id[i], pre);
        else {
            int p = ++tot;
            add(p, pre), add(p, -id[i]), pre = p;
            id[i] = -id[i];
        }
    }
}
void work() {
    queue<int> q;
    priority_queue<int, vector<int>, greater<int>> Q;
    auto push = [&](int u) {
        if (u <= n) Q.push(u);
        else q.push(u);
    };
    auto front = [&]() {
        int res;
        if (!q.empty()) res = q.front(), q.pop();
        else res = Q.top(), Q.pop();
        return res;
    };
    for (int i = 1; i <= tot; i++) if (!deg[i]) push(i);
    while (!q.empty() || !Q.empty()) {
        int u = front();
        if (u <= n) printf("%d %d ", u, u);
        for (int v: g[u]) if (--deg[v] == 0) push(v);
    }
    puts("");
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x; i <= n << 1; i++) {
        scanf("%d", &x);
        if (!L[x]) L[x] = i; else R[x] = i;
    }
    tot += n;
    iota(id + 1, id + n + 1, 1);
    sort(id + 1, id + n + 1, [&](int i, int j) { return L[i] < L[j]; });
    solve(1, n);
    work();
    return 0;
}


posted @ 2023-09-23 14:49  caijianhong  阅读(24)  评论(0编辑  收藏  举报