1. 程式人生 > 其它 >UOJ #37. 【清華集訓2014】主旋律(容斥+dp)

UOJ #37. 【清華集訓2014】主旋律(容斥+dp)

題目大意

給出一個 \(n\) 個點的有向圖,無重邊無自環,求其強連通的生成子圖個數。\(n\leq 15\)

前置知識:有向圖的 DAG 生成子圖計數

題目大意

給出一個 \(n\) 個點的有向圖,無重邊無自環,求其無環的生成子圖個數。\(n\leq 17\)

題意分析

注意到對於任意一個 DAG,總有一些入度為零的點。不妨欽定一些點入度為零,然後進行容斥計數。設 \(dp_S\) 表示 \(S\) 這個點集的 DAG 生成子圖個數。考慮列舉 \(S\) 的一個非空子集 \(T\),記其在 \(S\) 中的補集為 \(T'\),欽定 \(T\) 中的點入度為零。對於所有由 \(T\) 中某點指向 \(T'\)

中某點的有向邊,將其個數記為 \(num\),無論選擇與否,只要 \(T'\) 中選擇的邊構成一個 DAG,最終選擇的邊集就一定構成一個 DAG,同時滿足 \(T\) 中的點入度為零。因此對 \(dp_S\) 做出貢獻的方案數就為 \(2^{num}\times dp_{T'}\)。根據容斥的套路,若 \(|T|\) 為奇數則容斥係數為 \(1\),否則容斥係數為 \(-1\);這樣可以保證每一個有 \(m\) 個入度為零的點的合法方案被統計 \(\binom{m}{1}-\binom{m}{2}+\binom{m}{3}-\binom{m}{4}\cdots\) 次。因此可以寫出轉移方程:

\[dp_S=\sum_{T}(-1)^{|T|-1}\times 2^{num}\times dp_{T'} \]

時間複雜度

暴力做複雜度為 \(O(n\times 3^n)\),加一些簡單的小優化可以做到 \(O(3^n)\),具體看程式碼實現。

程式碼

#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 17, MAXM = 273, MOD = 1e9 + 7;
int n, m, dp[1 << MAXN], out[MAXN][1 << MAXN], in[MAXN][1 << MAXN], pw2[MAXM], ppcnt[1 << MAXN], val[1 << MAXN], low[1 << MAXN];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    pw2[0] = 1;
    for (int i = 1; i < MAXM; ++i) pw2[i] = pw2[i - 1] * 2 % MOD;
    cin >> n >> m;
    while (m--) {
        int u, v;
        cin >> u >> v, --u, --v;
        ++out[u][1 << v], ++in[v][1 << u];
    }
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int S = 0; S < 1 << n; ++S) {
                if (S >> j & 1) {
                    out[i][S] += out[i][S ^ 1 << j];
                    in[i][S] += in[i][S ^ 1 << j];
                }
            }
        }
    }
    for (int S = 1; S < 1 << n; ++S) ppcnt[S] = ppcnt[S >> 1] + (S & 1);
    for (int S = 1; S < 1 << n; ++S) if (!(S & 1)) low[S] = low[S >> 1] + 1;
    dp[0] = 1;
    for (int S = 1; S < 1 << n; ++S) {
        for (int T = S; T; T = (T - 1) & S) {
            if (T == S) {
                val[T] = 0;
            } else {
                int p = low[S ^ T], nT = T | 1 << p;
                val[T] = val[nT] - out[p][S ^ nT] + in[p][T];
            }
            dp[S] = (dp[S] + (ppcnt[T] & 1 ? 1ll : -1ll) * pw2[val[T]] * dp[S ^ T]) % MOD;
        }
    }
    cout << (dp[(1 << n) - 1] + MOD) % MOD << "\n";
    return 0;
}