3812: 主旋律
題意:一張有向圖,求它的生成子圖是強連通圖的個數。\(n \le 15\)
先說一個比較暴力的做法。
終於知道n個點圖的是DAG的生成子圖個數怎麼求了。
暴力列舉哪些點是一個scc,然後縮點,列舉入度為0的點,容斥原理dp DAG個數
\[
d(S) = \sum_{T \subset S, T \neq \varnothing}(-1)^{\mid T\mid-1}2^{w(T,S-T)}d(S-T)
\]
巧妙的做法是直接列舉縮點入度為0的點(即那些scc有哪些點)
\(f(S)\) S的生成子圖是強連通的個數
\(g(S)\) S拆成奇數個scc - S拆成偶數個scc. 原因是上面那個DP.
\(h(S)\) 誘導子圖S的邊數
\[
f(S) = 2^{h(S)} - \sum_{T \subset S, T \neq \varnothing} g(T) 2^{w(T, S-T)} 2^{h(S-T)} \\
g(S) = f(S) - \sum_{T \subset S, T \neq \varnothing,T\neq S,p\in T}f(T)g(S-T)
\]
注意要把拆成一個單獨拿出來,因為更新f用到的g不能是拆成一個!
感覺很巧妙,利用g,相當於把原來不同列舉方案中的DP一塊做了!
複雜度\(O(3^n)\)
程式碼中有一些實現技巧。特別注意怎樣固定p在T中
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = (1<<16) + 5, mo = 1e9+7;
inline int read() {
char c=getchar(); int x=0,f=1;
while(c<'0'||c>'9') {if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();}
return x*f;
}
int n, m, u, v, f[N], g[N], h[N], one[N], w[N], ins[N], outs[N];
ll mi[N];
int main() {
//freopen("in", "r", stdin);
n = read(); m = read();
for(int i=1; i<=m; i++) {
u = 1 << (read()-1); v = 1 << (read()-1);
outs[u] |= v; ins[v] |= u;
}
mi[0] = 1;
for(int i=1; i<=m; i++) mi[i] = mi[i-1] * 2 %mo;
for(int s = 1; s < 1<<n; s++) one[s] = 1 + one[s ^ (s & -s)];
for(int s = 1; s < 1<<n; s++) {
int p = s & -s, ns = s ^ p;
for(int t = ns; t; t = (t-1) & ns)
g[s] = (g[s] - (ll) f[s ^ t] * g[t]) %mo;
h[s] = h[ns] + one[ins[p] & ns] + one[outs[p] & ns];
f[s] = mi[h[s]];
for(int t = s; t; t = (t-1) & s) {
int p = (s^t) & -(s^t);
if(t == s) w[t] = 0;
else w[t] = w[t ^ p] - one[outs[p] & (s^t)] + one[ins[p] & t];
f[s] = (f[s] - (ll) g[t] * mi[w[t] + h[s^t]]) %mo;
}
if(f[s] < 0) f[s] += mo;
g[s] = (g[s] + f[s]) %mo;
}
printf("%d", f[(1<<n) - 1]);
}