题目大意:给定一张有向图,求这张有向图的生成子图中有多少强连通图
正着做不好做,我们考虑容斥原理
如果一个图不连通,那么这张图缩点之后一定会形成一个点数>=2的DAG
一个DAG中一定会有一些入度为0的点,我们枚举这些点的点集进行容斥
具体DP方程和细节见代码 注释写的还是比较详细的我就不多说了= =
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 16
#define MOD 1000000007
using namespace std;
int n,m,digit[1<<8];
int into[1<<15],out_of[1<<15];
long long f[1<<15],g[1<<15],h[1<<15];
long long power_2[M*M];
/*
f[S]表示点集S的生成子图强联通的方案数
g[S]表示点集S的生成子图G中,若G的所有联通块都强联通,则G对g[S]存在一个贡献
如果G中有奇数个连通块,则对g[S]的贡献为+1,否则为-1
h[S]表示点集S的诱导子图中有多少条边
f[S]=2^h[S]-Σ[T是S的非空子集]2^cnt*g[T]
其中cnt=|{x->y|x∈S,y∈S-T}|
(注意此时的g[S]不包含整个S强联通的情况)
*/
int Count(int x)
{
return digit[x>>8] + digit[x&255] ;
}
int main()
{
int i,j,x,y;
cin>>n>>m;
for(i=1;i<1<<8;i++)
digit[i]=digit[i>>1]+(i&1);
for(power_2[0]=1,i=1;i<=m;i++)
power_2[i]=(power_2[i-1]<<1)%MOD;
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
out_of[1<<x-1]|=1<<y-1;
into[1<<y-1]|=1<<x-1;
}
for(i=1;i<1<<n;i++)
{
int one=i&-i,sta=i^one;
//one为S集合中任意一点
//sta为S集合除掉one外剩余的点集
h[i]=h[sta]+Count(into[one]&sta)+Count(out_of[one]&sta);
for(j=sta;j;(--j)&=sta)//枚举与one不连通的点集
(g[i]+=MOD-f[i^j]*g[j]%MOD)%=MOD;
static int w[1<<15];//w[T]代表集合T中的点到集合S-T中的点的连边数量
f[i]=power_2[h[i]];
for(j=i;j;(--j)&=i)//枚举T集合
{
if(j==i)
w[j]=0;
else
{
int temp=(i^j)&-(i^j);//任选S-T集合中的一点
w[j]=w[j^temp]-Count((i^j)&out_of[temp])+Count(j&into[temp]);
}
(f[i]+=MOD-power_2[h[i^j]+w[j]]*g[j]%MOD)%=MOD;
}
(g[i]+=f[i])%=MOD;
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}