传送门:洛谷 CF2244C Stepan and Permutation | Codeforces C. Stepan and Permutation
更佳的阅读体验:CF2244C 题解


简要题意:给你一个排列,你可以任意次交换下标差恰好为 $x$ 或 $y$ 的元素,判断是否能使得排列变为升序。$n \le 2 \times 10^5$。

我们可以将每个下标看作一个节点,如果两个下标之差为 $x$ 或 $y$,就在它们之间连边。此时,一次操作就是交换图中一条边两端的元素。

由此我们发现,在一个连通块内,可以沿路径不断交换,使得连通块内的元素任意排序。但任意排序仅限这个连通块内部,所以元素无法移动到其他连通块里。

那我们需要判断的就变成了,对于每个元素,它在原序列中的位置和排序后应该到达的位置是否在同一连通块内。即,是否对于原序列中的任意元素都有 $p_i$ 与 $i$ 在同一连通块内。这一过程可以借助并查集来实现。

单组数据时间复杂度 $O(n \alpha(n))$。

#include <iostream>
#include <algorithm>
#include <numeric>
using namespace std;

const int N = 2e5 + 10;
int t, n, x, y, p[N], fa[N], rk[N];
bool flag;

int find(int x) {
    if (x == fa[x]) return x;
    return fa[x] = find(fa[x]);
}

void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return;
    if (rk[x] < rk[y]) fa[x] = fa[y];
    else if (rk[x] == rk[y]) fa[x] = fa[y], ++rk[y];
    else fa[y] = fa[x];
}

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    for (cin >> t; t; --t) {
        flag = false;
        cin >> n >> x >> y;
        for (int i = 1; i <= n; ++i) cin >> p[i];
        fill(rk + 1, rk + n + 1, 1);
        iota(fa + 1, fa + n + 1, 1);
        for (int i = 1; i <= x; ++i)
            for (int j = i; j <= n; j += x) merge(i, j);
        for (int i = 1; i <= y; ++i)
            for (int j = i; j <= n; j += y) merge(i, j);
        for (int i = 1; i <= n; ++i) if (find(i) != find(p[i])) {
            flag = true;
            break;
        } cout << (flag ? "NO\n" : "YES\n");
    } return 0;
}
最后修改:2026 年 07 月 17 日
如果觉得我的文章对你有用,请随意赞赏