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;
}
上一篇:linux-期望,进行交互,然后再次期望


下一篇:不需要的密码在源代码管理中的存储位置