mayoko’s diary

プロコンとかいろいろ。

Codeforces Round #319 (Div. 1) B. Invariance of Tree

解法

まず前提知識です。順列は必ずサイクルの集合で表すことが出来ます。例えば,
2 1 4 3
という順列を考えると, この順列は,
1 <-> 2
3 <-> 4
の交換によって表現されます。これは, (1, 2) というサイクルと (3, 4) というサイクルの集合で表せます。また, 例えば
6, 5, 4, 3, 1, 2
という順列を考えると, この順列は,
3 <-> 4
1 -> 6 -> 2 -> 5 (-> 1 -> 6 -> ...)
というふたつの数字交換で表せます。なので, (3, 4) というサイクルと (1, 6, 2, 5) というサイクルの集合で表せることになります。

この知識を使って問題を解いてみます。実は, サイクルの長さ(カッコの中に含まれる数字の数) が 2 以下のものが少なくとも 1 つはないと絶対に木を作ることは出来ません。まずはこの理由を考えます。

すべてのサイクルが 3 以上のとき, 自分のサイクルの中で辺を結ぼうとすると, 問題文の条件を満たそうとすると閉路が出来てしまいます。例えば, (i, j, k) というサイクルができているとしましょう。
i - j の間に辺を結ぶと, j - k の間にも辺を結ぶことになります(順列で考えると i 番目の数字が j で j 番目の数字が k になっているので)。で, j - k の間に辺を結ぶと同じようにして k と i の間にも辺を結ぶことになります。ということで,
i -> j -> k -> i
という閉路が出来てしまうことになり, 詰みです。また, i, j, k の間に辺を結ばないことにすると, 連結にするためには例えば
i -> (他のサイクルの点 a) -> ... -> j
とかしなければなりませんが, こうすると
j -> (他のサイクルの点 b) -> ... -> k
k -> (他のサイクルの点 c) -> ... -> i
となり結局閉路ができています。

以上により, すべてのサイクルが 3 以上の時は木が作れないことがわかりました。

では サイクルが 1 の点(つまり i == p[i] となる点)がある場合を考えます(この点を a とする)。この場合は簡単で, a と他の点を結べば OK です。

次にサイクルが 2 の点(i == p[p[i]])がある場合を考えます(a, b とする)。この場合は, 他のサイクルはすべて偶数で, それぞれのサイクルの偶数番目の頂点は a と, 奇数番目の頂点は b と結ばれるようにすれば OK です。

得た知見
  • 順列はサイクルに分割できる
    • ここで サイクル -> ループ という考えはすぐ出来たほうが良さそう
    • 木の条件を考える時ループがないか考えるのは普通だからね
const int MAXN = 100010;
int p[MAXN];
bool used[MAXN];

void solve(int n) {
    int index1 = -1, index2 = -1;
    for (int i = 0; i < n; i++) {
        if (i == p[i]) {
            index1 = i;
            break;
        } else if (i == p[p[i]]) {
            index2 = i;
        }
    }
    if (index1 != -1) {
        cout << "YES" << endl;
        for (int i = 0; i < n; i++) {
            if (i == index1) continue;
            cout << i+1 << " " << index1+1 << endl;
        }
        return;
    } else if (index2 != -1) {
        vector<pii> ans;
        used[index2] = used[p[index2]] = true;
        ans.emplace_back(index2, p[index2]);
        for (int i = 0; i < n; i++) {
            if (used[i]) continue;
            vector<int> v;
            int cur = i;
            while (1) {
                if (used[cur]) break;
                used[cur] = true;
                v.push_back(cur);
                cur = p[cur];
            }
            int size = v.size();
            if (size%2) {
                cout << "NO" << endl;
                return;
            } else {
                for (int i = 0; i < size; i++) {
                    if (i%2 == 0) ans.emplace_back(index2, v[i]);
                    else ans.emplace_back(p[index2], v[i]);
                }
            }
        }
        cout << "YES" << endl;
        for (auto p : ans) {
            cout << p.first+1 << " " << p.second+1 << endl;
        }
        return;
    } else cout << "NO" << endl;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> p[i];
        p[i]--;
    }
    solve(n);
    return 0;
}