设 (f(S)) 为点集 (S) 中强连通生成子图的方案数,(cnt(S)) 为点集 (S) 构成的诱导子图中边的个数。考虑用 (2^{cnt(S)}) 减去不合法方案数来计算 (f(S)),不合法情况即为缩点后为 (DAG)。
设 (h(S)) 为点集 (S) 缩点后构成点数 (>1) 的 (DAG) 的方案数。考虑枚举一个点集 (T),其由若干个出度为 (0) 的强连通分量构成,那么点集 (S-T) 内部的边和连到 (T) 的边可以任意选取。但这样会算重,如果存在两个出度为 (0) 的强连通分量 (x,y),(T=x) 时和 (T=y) 时都会算一遍,也就是合并后点集 (S-T) 中也可能有出度为 (0) 的强连通分量。因此还需进行容斥,得:
[large h(S)=sum_{T subseteq S,T
e varnothing} (-1)^{size(T)-1}2^{e(S-T,T)}h(S-T)
]
其中 (size(T)) 为点集 (T) 中强连通分量个数,(e(S-T,T)) 为点集 (S-T) 到点集 (T) 的边数。
因为需要枚举强连通分量,所以复杂度无法接受。考虑将容斥系数放到 (DP) 状态中,设 (g(S)) 为点集 (S) 构成奇数个强连通分量的方案数减去构成偶数个强连通分量的方案数。转移就是枚举点集 (T),其为单独的一个强连通分量,为避免算重,还需确定一个点 (x),让 (T) 一直包含 (x),得:
[largeegin{aligned}
g(S)&=f(S)-sum_{T subset S,x in T} f(T)g(S-T) \
h(S)&=sum_{T subseteq S,T
e varnothing} 2^{cnt(S)-e(T,S)}g(T)\
f(S)&=2^{cnt(S)}-h(S)
end{aligned}
]
在转移第 (f(S)) 时,要注意还不能把 (f(S)) 的贡献给 (g(S)) 算进去。
复杂度为 (O(3^n))。
#include<bits/stdc++.h>
#define maxn 35010
#define p 1000000007
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
template<typename T> inline void read(T &x)
{
x=0;char c=getchar();bool flag=false;
while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
if(flag)x=-x;
}
int n,m,all;
ll pw[maxn],siz[maxn],cnt[maxn],e[maxn],f[maxn],g[maxn],in[maxn],out[maxn];
void dfs(int s,int t)
{
if(s&(t-1)) dfs(s,s&(t-1));
e[t]=e[t-lowbit(t)]+siz[in[lowbit(t)]&s];
}
int main()
{
read(n),read(m),all=(1<<n)-1,pw[0]=1;
for(int i=1;i<=m;++i)
{
int x,y;
read(x),read(y),x--,y--;
in[1<<y]|=(1<<x),out[1<<x]|=(1<<y);
}
for(int i=1;i<=m;++i) pw[i]=pw[i-1]*2%p;
for(int s=1;s<=all;++s)
siz[s]=siz[s-lowbit(s)]+1,cnt[s]=cnt[s-lowbit(s)]+siz[in[lowbit(s)]&s]+siz[out[lowbit(s)]&s];
for(int s=1;s<=all;++s)
{
f[s]=pw[cnt[s]],dfs(s,s);
for(int t=s-lowbit(s);t;t=((t-1)&(s-lowbit(s)))) g[s]=(g[s]-f[s^t]*g[t]%p+p)%p;
for(int t=s;t;t=(t-1)&s) f[s]=(f[s]-pw[cnt[s]-e[t]]*g[t]%p+p)%p;
g[s]=(g[s]+f[s])%p;
}
printf("%lld",f[all]);
return 0;
}