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;
}
投稿日時:
最終更新: