zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    Java Web学习总结(9)——servlet和Jsp生命周期解读
    Java Web学习总结(9)——servlet和Jsp生命周期解读
    Java Web学习总结(10)——Session详解
    Java Web学习总结(10)——Session详解
    Mysql学习总结(6)——MySql之ALTER命令用法详细解读
    Mysql学习总结(6)——MySql之ALTER命令用法详细解读
    十大热门问题集锦,一眼看懂华为云数据库黑科技
    python上下文管理器细读
    关于token你需要知道的
    使用app测试Modelarts在线服务
  • 原文地址:https://www.cnblogs.com/Foreign/p/15505428.html
Copyright © 2011-2022 走看看