公式
D - 研究テーマの選定 / Selection of Research Topic 解説
by
D - 研究テーマの選定 / Selection of Research Topic 解説
by
MMNMM
\(\lbrace1,2,\ldots,N\rbrace\) の部分集合 \(2 ^ N\) 通りを全探索することを考えます。
部分集合 \(S\) が満たすべき条件は、すべての \(j\) について \(U _ j\in S\implies V _ j\in S\) です。 \(a\implies b\) は \(\neg\,a\vee b\) と同値なので、すべての \(j\) について \(U _ j\notin S\vee V _ j\in S\) が成り立つかを検証すればよいです。
条件を満たしている部分集合 \(S\) に対して \(\displaystyle\sum _ {i\in S}(P _ i-C _ i)\) の値を求めることは \(O(|S|)\) 時間で可能です。
よって、この問題を \(O((N+M)2 ^ N)\) 時間で解くことができました。
実装例は以下のようになります。
#include <iostream>
#include <vector>
using namespace std;
int main() {
int N, M;
cin >> N >> M;
// S[i] := P[i] - C[i] を計算しておく
vector<int> S(N);
for (int& s : S) {
int P, C;
cin >> P >> C;
s = P - C;
}
// 満たすべき関係
vector<pair<int, int>> edges(M);
for (auto& [u, v] : edges) {
cin >> u >> v;
--u; // 0-indexed にしておく
--v;
}
int ans = 0;
// 部分集合を全部試す
for (int bit = 0; bit < 1 << N; ++bit) {
// ∑ S[i] を求める
int tmp = 0;
for (int i = 0; i < N; ++i) {
if (1 & (bit >> i)) {
tmp += S[i];
}
}
// 条件を満たしているか判定
bool ok = true;
for (auto [u, v] : edges) {
if (1 & (bit >> u) && 1 & ~(bit >> v)) { // 満たされていない関係があったら
ok = false; // ダメ
}
}
if (ok) {
ans = max(ans, tmp);
}
}
cout << ans << endl;
return 0;
}
N, M = map(int, input().split())
# S[i] := P[i] - C[i] を計算しておく
S = []
for i in range(N):
P, C = map(int, input().split())
S.append(P - C)
# 満たすべき関係
edges = [tuple(map(lambda x: int(x) - 1, input().split())) for i in range(M)]
ans = 0
# 部分集合を全部試す
for bit in range(1 << N):
# ∑ S[i] を求める
tmp = sum(s for i, s in enumerate(S) if 1 & (bit >> i))
# 条件を満たしているか判定
for u, v in edges:
if 1 & (bit >> u) and 1 & ~(bit >> v): # 満たされていない関係があったら
break # ダメ
else:
ans = max(ans, tmp)
print(ans)
投稿日時:
最終更新:
