【JZOJ6124】有限空间跳跃理论

Description

给出一个无向连通图,求给每条边定向后是DAG(有向无环图)的方案数,两种方案不同当且仅当存在一条边它们的方向不同。

Solution

f S f_S fS表示集合s的点在DAG上的方案数,转移时枚举一个独立集 T T T表示度数为0的点,大概转移是这样: f S = ∑ T ⊂ S f S − T ( − 1 ) ∣ T ∣ − 1 f_S=\sum_{T\subset S} f_{S-T}(-1)^{|T|-1} fS=TSfST(1)T1
直接枚举是 3 n 3^n 3n的,子集卷积一下即可。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;++i)
#define fd(i,j,k) for(int i=j;i>=k;--i)
#define rep(i,x) for(int i=ls[x];i;i=nx[i])
using namespace std;
typedef long long ll;
const int N=22,mo=1e9+7;
void inc(int &x,int y){
	x=x+y>=mo?x+y-mo:x+y;
}
void fwt(int *a,int n,int sig){
	for(int m=2;m<=n;m<<=1)
	for(int i=0,hf=m>>1;i<n;i+=m)
	fo(j,i,i+hf-1) inc(a[j+hf],sig>0?a[j]:mo-a[j]);
}
int a[N][N],d[N];
int f[N][1<<20],g[N][1<<20];
int cn[1<<20];
int main()
{
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	int n,m;
	scanf("%d %d",&n,&m);
	fo(i,1,m){
		int u,v;
		scanf("%d %d",&u,&v);
		a[u][v]=a[v][u]=1;
	}
	fo(s,1,(1<<n)-1){
		cn[s]=cn[s-(s&-s)]+1; 
		int now=1,tot=0;
		fo(i,1,n) if(s&(1<<i-1)) d[++tot]=i;
		fo(i,1,tot-1){
			fo(j,i+1,tot) if(a[d[i]][d[j]]) {now=0;break;}
			if(!now) break;
		}
		g[cn[s]][s]=(now*(cn[s]&1?1:-1)+mo)%mo;
	}
	fo(i,0,n-1) fwt(g[i],1<<n,1);
	f[0][0]=1;
	fo(i,1,n){
		fwt(f[i-1],1<<n,1);
		fo(j,0,i-1)
		fo(k,0,(1<<n)-1) inc(f[i][k],(ll)f[j][k]*g[i-j][k]%mo);
		fwt(f[i],1<<n,-1);
		fo(j,0,(1<<n)-1) if(cn[j]!=i) f[i][j]=0;
	}
	printf("%d\n",f[n][(1<<n)-1]);
}
posted @ 2019-04-18 21:59  sadstone  阅读(89)  评论(0编辑  收藏  举报