ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Atcoder Grand Contest 001 F - Wide Swap 题解

2021-11-03 20:04:04  阅读:196  来源: 互联网

标签:Wide return Atcoder int 题解 inv mid pos query


旅行传送门

题意:给定一个长度为 \(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;
}

标签:Wide,return,Atcoder,int,题解,inv,mid,pos,query
来源: https://www.cnblogs.com/Foreign/p/15505428.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有