BZOJ 3812 主旋律 状压DP+容斥原理

题目大意:给定一张有向图,求这张有向图的生成子图中有多少强连通图

正着做不好做,我们考虑容斥原理

如果一个图不连通,那么这张图缩点之后一定会形成一个点数>=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;
}


你可能感兴趣的:(容斥原理,bzoj,状压dp,BZOJ3812)