公式

F - Make Isomorphic 解説 by MMNMM


まず、同じ \((i,j)\) に対して \(2\) 回以上操作をする意味はありません。 よって、それぞれの \((i,j)\) に対しては \(0\) 回操作するか \(1\) 回操作するかのどちらかです。

\(H\) の各頂点が \(G\) のどの頂点に対応するかを固定することで、\((i,j)\) に対して操作すべき回数がわかります。 よって、この対応について考えると次の問題に言い換えることができます。

\(P\) が \((1,2,\ldots,N)\) の並べ替えすべてをわたるとき、「\(G\) に辺 \((P _ i,P _ j)\) が含まれるか」と「\(H\) に辺 \((i,j)\) が含まれるか」が異なる \(i,j\) に対する \(A _ {i,j}\) の総和の最小値を求めよ。

この問題の制約で \(N\leq8\) なので、ありえる \(P\) はたかだか \(40320\) 通りです。 それぞれに対して、ありえる辺 \((i,j)\ (1\leq i\lt j\leq N)\) を \(\dfrac{N(N-1)}2\) 通りすべて確かめ、総和を計算しても十分高速です。

並べ替えすべてを列挙するためには、再帰関数を使ったり、C++ などでは std::next_permutation などの標準で用意されている関数を使ったりすることができます。

実装例は以下のようになります。

以下の実装例では、グラフに辺が含まれているかを std::set を用いて判定しているため、全体の計算量は \(\Theta(N!\,N ^ 2\log N)\) となっています。

#include <iostream>
#include <set>
#include <utility>
#include <vector>
#include <numeric>
#include <algorithm>

int main() {
    using namespace std;
    int N;
    cin >> N;

    // グラフ G, H に含まれる辺の集合
    set<pair<int, int>> edges_G, edges_H;
    
    int M_G;
    cin >> M_G;
    for (int i = 0; i < M_G; ++i) {
        int u, v;
        cin >> u >> v;
        edges_G.emplace(u - 1, v - 1);
        edges_G.emplace(v - 1, u - 1); // 逆向きの辺も追加しておく
    }
    
    int M_H;
    cin >> M_H;
    for (int i = 0; i < M_H; ++i) {
        int a, b;
        cin >> a >> b;
        edges_H.emplace(a - 1, b - 1);
        edges_H.emplace(b - 1, a - 1); // 逆向きの辺も追加しておく
    }

    vector A(N, vector<int>(N));
    for (int i = 0; i < N; ++i) {
        for (int j = i + 1; j < N; ++j) {
            cin >> A[i][j];
            A[j][i] = A[i][j]; // 逆向きのコストも追加しておく
        }
    }

    // H の頂点を G の頂点に対応させる並べ替え
    vector<int> P(N);
    iota(begin(P), end(P), 0);

    int ans{28000000}; // 答えは 8 * (8 - 1) / 2 * 10^6 以下
    
    // すべての並べ替えを探索
    do {
        int sum = 0;
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < i; ++j) {
                // H に (i, j) が含まれて G に (P[i], P[j]) が含まれない か、
                // H に (i, j) が含まれずに G に (P[i], P[j]) が含まれる 場合 A[i][j] を足す
                sum += A[i][j] * (edges_H.contains({i, j}) != edges_G.contains({P[i], P[j]}));
            }
        }
        // 最小値を更新
        ans = min(ans, sum);
    } while(ranges::next_permutation(P).found);

    cout << ans << endl;
    return 0;
}

投稿日時:
最終更新: