Loading

P11714 [清华集训 2014] 主旋律

很牛的计数。

正难则反,考虑图不是一个 SCC 的情况,那么缩点后就是一个有 \(> 1\) 个点的 DAG,考虑 DAG 怎么计数,由于每个 DAG 都有若干个入度为 \(0\) 的点,且拿掉这些点之后剩下的还是一个 DAG,这就有子结构了,方便我们 DP。

考虑 \(f_S\) 表示 \(S\) 中的点形成一个 SCC 的方案数,假设 \(E(S, T)\) 表示 \(\sum_{u \in S, v \in T} [(u, v) \in E]\),则 \(f_S = 2^{E(S, S)} - ?\)\(?\) 表示形成若干 SCC 的方案数,那么假设我们拿出了点集 \(T\),形成了 \(k\)\(SCC\),那么容斥系数为 \((-1)^{k + 1}\),设方案数为 \(g_{T, k}\),则 \(f_S = 2^{E(S, S)} - \sum\limits_{T \subset S, T \neq \varnothing} (-1)^{k + 1}g_{T, k} 2^{E(T, T) + E(T, S - T)}\),后面那项是因为这些 SCC 缩点后入度为 \(0\)

然后考虑把容斥系数放里面,即 \(g_T\) 表示 \(\sum\limits_k (-1)^{k + 1}g_{T, k}\),也就是“形成奇数个 SCC 的方案数”减去“形成偶数个 SCC 的方案数”。

那么考虑转移,\(g_S = f_S - \sum\limits_{T \subseteq S, \text{lowbit}(T) = \text{lowbit}(S)} f_Tg_{S - T}\)
前面一项是因为 \(f_S\) 形成了一个 SCC,后面是因为相较于 \(g_{S - T}\),新增加了一个 SCC 为 \(T\),奇偶反转,所以要减去,另外 \(\text{lowbit}\) 强制要求选,保证了不算重。

然后有一个考虑就是枚举到 \(S\) 的时候先算 \(g_S\),再算 \(f_S\),最后让 \(g_S \leftarrow g_S + f_S\),因为 \(f_S\) 是单独组成一个 SCC,不能减掉,换句话说,这里 \(g_S\) 加上 \(f_S\) 是为了后面的 \(f\) 服务的。

最后考虑 \(E(S, S)\)\(E(T, S - T)\),枚举 \(\text{lowbit}\) 转移即可,后者就对于每个 \(S\) 算一下,没啥好讲的。

时间复杂度 \(\mathcal{O}(3^n)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 1 << 15, M = 15 * 14, mod = 1e9 + 7;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    cout << arg << ' ';
    dbg(args...);
}
namespace Loop1st {
#define lowbit(s) ((s) & -(s))
#define popc __builtin_popcount
int n, m, E1[N], E2[N], in[N], out[N], f[N], g[N], pw[M + 1];
void main() {
    cin >> n >> m;
    pw[0] = 1;
    for (int i = 1; i <= m; i++) pw[i] = (pw[i - 1] << 1) % mod;
    for (int i = 0, u, v; i < m; i++) {
        cin >> u >> v; u--; v--;
        in[1 << v] |= (1 << u); out[1 << u] |= (1 << v);
    }
    for (int s = 0; s < (1 << n); s++) {
        int x = lowbit(s);
        E1[s] = E1[s - x] + popc(in[x] & s) + popc(out[x] & s);
    }
    for (int s = 1; s < (1 << n); s++) {
        E2[s] = 0;
        for (int t = (s - 1) & s; t; t = (t - 1) & s) {
            int x = lowbit(s - t);
            E2[t] = E2[t + x] - popc(out[x] & (s - t)) + popc(in[x] & t);
        }
        for (int t = (s - 1) & s; t; t = (t - 1) & s) if (lowbit(s) == lowbit(t)) {
            g[s] = (g[s] - (ll)f[t] * g[s - t] % mod + mod) % mod;
        }
        f[s] = pw[E1[s]];
        for (int t = s; t; t = (t - 1) & s) {
            f[s] = (f[s] - (ll)g[t] * pw[E1[s - t] + E2[t]] % mod + mod) % mod;
        }
        g[s] = (g[s] + f[s]) % mod;
    }
    cout << f[(1 << n) - 1] << '\n';
}

}
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T--) Loop1st::main();
    return 0;
}
// start coding at 18:05
// finish debugging at 18:32
posted @ 2026-01-09 18:55  循环一号  阅读(17)  评论(0)    收藏  举报