公式

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)

投稿日時:
最終更新: