1. 程式人生 > 其它 >Atcoder Grand Contest 001 F - Wide Swap 題解

Atcoder Grand Contest 001 F - Wide Swap 題解

旅行傳送門

題意:給定一個長度為 \(N\) ,正好包含 \(1\) ~ \(N\) 的序列 \(P_1 \cdots P_N\) ,你可以執行以下操作任意次:

  • 選取兩個下標 \(i,j\) ,當滿足 \(j - i \geq K\)\(|P_i-P_j| = 1\) 時,你可以交換 \(P_i\)\(P_j\) 的值。

求最終可能得到的字典序最小的排列。

題目分析

很好的一道題。

  • 逆置換

現在的題面難以下手,因此不妨設 \(Q\)\(P\) 的逆置換,即 \(Q_{Pi} = i\) ,於是題面轉化為如果 \(|Q_i - Q_{i \pm 1}| \geq K\)

,那麼就可以交換 \(i\)\(i \pm 1\) 這兩個相鄰的元素。

我覺得這一點的思想類似於多維偏序的問題,前者是由多維向低維的轉化,而本題則是對判斷條件由多到少的轉化。

同時,要使 \(P\) 字典序最小,等價於要使 \(Q\) 字典序最小,在此給出證明:

假設當前要填的最小數為 \(j\) ,現有位置 \(i\)\(i'\) ,且 \(i < i'\) ,對 \(Q_i = j\)\(Q_{i'} = j\) ,當還原為原序 \(P\) 時,有 \(P_j = i\)\(P_j = i'\) ,顯然前者的字典序小於後者,得證。

  • 反向拓撲排序

考慮 \(Q\)

中的兩個值 \(Q_i\)\(Q_j\) ,不難發現當 \(|Q_i - Q_j| < K\) 時, \(Q_i\)\(Q_j\) 的相對位置(即先後順序)無論如何操作都不會發生變化,因為序列 \(Q\) 中僅有相鄰的兩個數可能發生交換,所以 \(Q_i\)\(Q_j\) 的相對位置要發生改變,兩者必須交換一次,否則的話它們的關係就被鎖死了,也就是上述說的相對位置固定了。

因此對某個 $Q_i (1 \leq i \leq N) $ ,對其後方的 \(Q_j (i < j \leq N)\) ,有:

  • \(Q_j \in [1,Q_i - k] \bigcup [Q_i + k,n]\)
    ,我們無法確定二者的相對位置關係
  • \(Q_j \in [Q_i - k + 1,Q_i - 1] \bigcup [Q_i + 1,Q_i + k + 1]\)\(Q_j\) 必排在 \(Q_i\) 的後方

相對位置無法改變不難想到就是拓撲排序,而要求較小的數下標儘可能小更是經典問題,建反圖跑拓撲排序(附上例題):

①號傳送門

②號傳送門

  • 線段樹優化建圖

現在我們得到了一個樸素的 \(O(nk)\) 演算法:對於每個 \(Q_i\) ,向其後方所有 \(Q_j \in [Q_i - k + 1,Q_i - 1] \bigcup [Q_i + 1,Q_i + k + 1]\) 連一條有向邊 \(Q_j \rightarrow Q_i\) (注意是反向建圖),然後跑一遍拓撲序就能出答案。

但實際上,由於傳遞性(\(a\rightarrow b , b\rightarrow c \Rightarrow a \rightarrow c\)),我們是沒有必要把邊連滿的,所以我們從後往前列舉,對於當前位置的 \(Q_i\) ,只需分別向在兩個範圍內最先出現的 \(Q_j\)\(Q_{j'}\) 連兩條邊即可,因為兩者的區間有重疊,所以 \(Q_j\)\(Q_{j'}\) 對之後 \([Q_i - k + 1,Q_i - 1]\)\([Q_i + 1,Q_i + k + 1]\) 內的點一定存在連邊,從而使得 \(Q_i\) 間接地連線了區間內所有符合要求的點,時間複雜度 \(O(nlogn)\)

最後注意不要忘了還原成原序列。

AC程式碼

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
const int inf = 0x3f3f3f3f;
const int maxn = 5e5 + 5;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int n, k;
int inv[maxn], deg[maxn], ans[maxn];
std::vector<int> e[maxn];
std::priority_queue<int> q;

#define lson k << 1
#define rson k << 1 | 1
struct node
{
    int l, r, min;
} tree[maxn << 2];

inline void pushup(int k) { tree[k].min = std::min(tree[lson].min, tree[rson].min); }

void build(int k, int l, int r)
{
    tree[k].l = l, tree[k].r = r;
    if (l == r)
    {
        tree[k].min = inf;
        return;
    }
    int mid = (l + r) >> 1;
    build(lson, l, mid);
    build(rson, mid + 1, r);
    pushup(k);
}

void update(int k, int p, int x)
{
    if (tree[k].l == tree[k].r)
    {
        tree[k].min = x;
        return;
    }
    int mid = (tree[k].l + tree[k].r) >> 1;
    p <= mid ? update(lson, p, x) : update(rson, p, x);
    pushup(k);
}

int query(int k, int l, int r)
{
    if (l > r)
        return inf;
    if (l <= tree[k].l && tree[k].r <= r)
        return tree[k].min;
    int mid = (tree[k].l + tree[k].r) >> 1;
    if (r <= mid)
        return query(lson, l, r);
    else if (l > mid)
        return query(rson, l, r);
    else
        return std::min(query(lson, l, mid), query(rson, mid + 1, r));
}

void topo()
{
    int cnt = n;
    rep(i, 1, n) if (!deg[i]) q.push(i);
    while (!q.empty())
    {
        int u = q.top();
        q.pop();
        inv[cnt--] = u;
        for (auto v : e[u])
        {
            --deg[v];
            if (!deg[v])
                q.push(v);
        }
    }
}

int main(int argc, char const *argv[])
{
    n = read(), k = read();
    rep(i, 1, n) inv[read()] = i;
    build(1, 1, n);
    down(i, n, 1)
    {
        //分別找兩個範圍內最先出現的點
        int pos = query(1, inv[i] + 1, std::min(inv[i] + k - 1, n));
        if (pos ^ inf)
            //反向建圖,反向連邊
            e[inv[pos]].push_back(inv[i]), ++deg[inv[i]];
        pos = query(1, std::max(1, inv[i] - k + 1), inv[i] - 1);
        if (pos ^ inf)
            e[inv[pos]].push_back(inv[i]), ++deg[inv[i]];
        //加入當前點的貢獻
        update(1, inv[i], i);
    }
    topo();
    //還原成原序列
    rep(i, 1, n) ans[inv[i]] = i;
    rep(i, 1, n) printf("%d\n", ans[i]);
    return 0;
}