题目大意:给定一张无向图,求这张无向图的生成子图中有多少强连通图
正着做不好做,我们考虑容斥原理
如果一个图不连通,那么这张图缩点之后一定会形成一个点数>=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^(h[S]-h[T])*g[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; }
时间: 2024-11-05 20:48:05