BZOJ3812 主旋律(状压dp+容斥原理)

  设f[S]为S点集是SCC的方案数。考虑通过去掉不合法方案转移。可以枚举入度为0的SCC所含点集S',这样显然S^S'内部的边和由S'连向S^S'的边删还是不删任选。但是这样无法保证S'包含所有入度为0的SCC,于是考虑容斥,瞎猜可以得到容斥系数与SCC数量有关,于是设g[i][S]为S包含i个无关SCC的方案数,转移有f[S]=2cnt(S)-Σ(-1)j*g[j][S']*2cnt(S^S')+cnt(S' to S^S'),g的转移通过枚举编号最小点所在SCC实现。注意到g[j][]的贡献只与j的奇偶性有关,于是可以改成g[S]为S有偶数个无关SCC的方案数,h[S]为S有奇数个无关SCC的方案数,转移类似。

#include<iostream> 
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 15
#define P 1000000007
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
	int x=0,f=1;char c=getchar();
	while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}
int n,m,a[N][N],e[1<<N],s[N][1<<N],p[N*N*N],f[1<<N],g[1<<N],h[1<<N];
int trans(int x)
{
	int s=-1;
	while (x) s++,x>>=1;
	return s;
}
int calc(int x,int y)
{
	int u=0;
	for (int i=0;i<n;i++)
	if (x&(1<<i)) u+=s[i][y];
	return u;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	const char LL[]="%I64d\n";
#else
	const char LL[]="%lld\n";
#endif
	n=read(),m=read();
	for (int i=1;i<=m;i++)
	{
		int x=read()-1,y=read()-1;
		a[x][y]=1;
	}
	for (int i=0;i<(1<<n);i++)
		for (int x=0;x<n;x++)
		if (i&(1<<x))
			for (int y=0;y<n;y++)
			if (i&(1<<y)) e[i]+=a[x][y];
	for (int i=0;i<n;i++)
		for (int j=1;j<(1<<n);j++)
		s[i][j]=s[i][j^(j&-j)]+a[i][trans(j&-j)];
	p[0]=1;for (int i=1;i<=m;i++) p[i]=(p[i-1]<<1)%P;
	for (int i=1;i<(1<<n);i++)
	{
		f[i]=p[e[i]];
		int x=0;
		for (int j=0;j<n;j++) if (i&(1<<j)) {x=j;break;}
		for (int j=i-1&i;j;j=j-1&i)
		if (j&(1<<x))
		{
			g[i]=(g[i]+1ll*h[i^j]*f[j])%P;
			h[i]=(h[i]+1ll*g[i^j]*f[j])%P;
		}
		for (int j=i;j;j=j-1&i)
		f[i]=(f[i]+1ll*(g[j]+P-h[j])*p[e[i^j]+calc(j,i^j)]%P)%P;
		h[i]=(h[i]+f[i])%P;
	}
	cout<<f[(1<<n)-1];
	return 0;
}

  

posted @ 2019-04-04 00:32  Gloid  阅读(285)  评论(0编辑  收藏  举报