第二回全国統一プログラミング王決定戦予選 C - Swaps

C - Swaps

問題

長さ $n$ の数列 $(a_i), (b_i)$ が与えられる. $(a_i)$ に対して 2 点スワップを $n - 2$ 回まで行うことで,任意の $i$ について $a_i \leq b_i$ とできるか判定せよ.

制約

  • $2 \leq n \leq 10^5$
  • $1 \leq a_i, b_i \leq 10^9$

考察

まず適当に並び替えることで, $(b_i)$ を昇順としてよい.

ここで $(a_i)$ を昇順にしたものを $(a'_i)$ としたとき, $a'_i \gt b_i$ なる $i$ が存在すればどれだけスワップしても条件は満たせない.

そうでない場合, $n - 1$ 回スワップすれば $(a_i)$ をソートできるため,条件は満たせる. これと同様に, $n - 2$ 回の 2 点スワップによって $n - 2$ 要素をソート済みにする(つまり $a_i = a'_i$ にする)ことが必ずできる. そこで $b_i \leq a'_{i + 1}$ なる $i$ が存在すれば, $a'_i$ と $a'_{i + 1}$ はソートする必要がない.よってこのときは OK となる.

そうでない場合,必ず $(a_i)$ は昇順にソートしなくてはならない.さらに上の条件から, $(a_i)$ は全要素異なる. よって「長さ $n$ の順列を $n - 2$ 回の 2 点スワップでソートできるか?」という問題に帰着できる.

色々考えてみると,「頂点数 - 順列グラフの強連結成分数 」回の 2 点スワップで順列はソートできることが分かる. よって UnionFind などを使えば簡単に判定できる.

ちなみに「必要な 2 点スワップの回数の最小値」は(揃える順番に依らず)貪欲に揃えていった場合と同じになるので,シミュレーションで判定することもできる.本番はそうしていた.

実装例

提出 #8374236 - 第二回全国統一プログラミング王決定戦予選

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>
#include <map>

struct UnionFind { ... };

void fail() { ... }
void succ() { ... }

int main() {
    int n;
    std::cin >> n;

    std::vector<int> a(n), b(n);
    for (auto& x : a) std::cin >> x;
    for (auto& x : b) std::cin >> x;

    {
        // bを昇順にする
        std::vector<int> idx(n);
        std::iota(idx.begin(), idx.end(), 0);
        std::sort(idx.begin(), idx.end(),
                  [&](int i, int j) { return b[i] < b[j]; });

        auto oa = a, ob = b;
        for (int i = 0; i < n; ++i) {
            a[i] = oa[idx[i]];
            b[i] = ob[idx[i]];
        }
    }

    // そもそも達成可能か
    auto sa = a;
    std::sort(sa.begin(), sa.end());
    for (int i = 0; i < n; ++i) {
        if (sa[i] > b[i]) fail();
    }

    // 全体をソートしなくて良いか
    for (int i = 0; i + 1 < n; ++i) {
        if (sa[i + 1] <= b[i]) succ();
    }

    // 座圧して順列に変換
    std::map<int, int> reva;
    for (int i = 0; i < n; ++i) reva[sa[i]] = i;
    for (auto& x : a) x = reva[x];

    // 連結成分数でスワップ回数を判定
    UnionFind uf(n);
    for (int i = 0; i < n; ++i) uf.unite(i, a[i]);

    if (uf.gnum > 1) {
        succ();
    } else {
        fail();
    }
    return 0;
}
Built with Hugo
テーマ StackJimmy によって設計されています。