公式

G - Peaceful Teams 解説 by MMNMM


この問題は、選手を順にチームに追加していく方法と、チームごとにまとめて決定する方法で解くことができます。

1. 選手を順にチームに追加する解法 \(O(N\times N!)\) 時間

\(1,2,\ldots,i-1\) 番目の選手を、互いに相性の悪い選手が同じチームにならないようにたかだか \(T\) チームに分けているとします。

ここに \(i\) 番目の選手を追加するには、たかだか \(T\) チームのいずれかに \(i\) 番目の選手を入れるか、(チーム数が \(T\) より少ないとき)新しく \(i\) 番目の選手しかいないチームを作るかのどちらかです。

この処理を再帰関数を用いて書くことで、\(O(N\times N!)\) 時間でこの問題を解くことができます。

\(i\) 番目の選手と相性が悪い選手や現在のチーム分けを連続する \(N\operatorname{bit}\) で保存することで、ワードサイズ \(w\) に対して \(O(N/w\times N!)\) 時間とすることもできます。

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

#include <iostream>
#include <vector>

using namespace std;

unsigned N, T, M;
vector<unsigned> hate, teams;

// 再帰関数でチーム分け
unsigned dfs(unsigned now) {
    // 最後の選手まで見て T チームに分かれていれば OK
    if (now == N)
        return size(teams) == T;

    unsigned ans{};

    // すでにあるチームに now 番目の選手を追加する
    for (auto &&team : teams)
        // チームに now 番目の選手と相性の悪い選手がいなければ
        if (!(team & hate[now])) {
            team ^= 1U << now;
            ans += dfs(now + 1);
            team ^= 1U << now;
        }

    // チーム数に余裕があるとき、新しいチームを作る
    if (size(teams) < T) {
        teams.emplace_back(1U << now);
        ans += dfs(now + 1);
        teams.pop_back();
    }

    return ans;
}

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

    // hate[i] の j ビットめが 1 ⟹ i 番目の選手と j 番目の選手の相性が悪い (0-indexed)
    hate = vector<unsigned>(N);
    for (unsigned i{}, a, b; i < M; ++i) {
        cin >> a >> b;
        hate[--b] |= 1U << --a;
    }

    teams.reserve(T);
    cout << dfs(0) << endl;

    return 0;
}

2. チームごとにメンバーをまとめて決定する解法 \(O(3^NT)\) 時間

\(\operatorname{dp}[S][t]\coloneqq\bigl(S\) に含まれる選手を、互いに相性の悪い選手を含まない \(t\) チームに分ける場合の数 \(\bigr)\) と定めます。

\(S\) の(二進法に基づいて非負整数へ変換した値による)昇順に DP テーブルを走査し、\(\{1,2,\ldots,N\}\setminus S\) のうち最小の整数に対応する選手が属するチームのメンバーを全探索することで、DP テーブルを埋めることができます(メモ化再帰でも実装可能です)。

\(O(2^N\operatorname{poly}(N))\) 時間の前計算を行って、相性の悪い選手の組を含まないチームをすべて列挙しておくことで、計算量を \(O(3^NT)\) 時間とできます。

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

#include <iostream>
#include <vector>
#include <bitset>

int main() {
    using namespace std;
    unsigned N, T, M;
    cin >> N >> T >> M;

    vector<unsigned> hate(N);
    for(unsigned i{}, a, b; i < M; ++i){
        cin >> a >> b;
        hate[--b] |= 1U << --a;
    }

    // possible_team[S] := S からなるチームを作ったとき、相性の悪い選手の組がいない
    // O(2^N N^2/w) 時間
    bitset<1024> possible_team;
    for(unsigned i{}; i < 1U << N; ++i){
        unsigned m{};
        for(unsigned j{}; j < N; ++j)if(1U & (i >> j))m |= hate[j];
        if(!(i & m))
            possible_team[i] = true;
    }

    vector dp(1U << N, vector<unsigned>(T + 1));
    dp.front().front() = 1;
    for(unsigned i{}; i < 1U << N; ++i)
        // 残っているうち番号の一番小さい選手が属するチームを全探索
        for(unsigned c{i + 1 | i}, j{c}; j < 1U << N; ++j |= c)
            if(possible_team[j ^ i])
                for(unsigned k{}; k < T; ++k)
                    dp[j][k + 1] += dp[i][k];

    cout << dp.back().back() << endl;

    return 0;
}

投稿日時:
最終更新: