BZOJ3812 清华集训2014 主旋律

 

直接求出强联通生成子图的数量较难,不妨用所有生成子图的数量减去非强联通的。

非强联通生成子图在所点后满足编号最小的点所在的强联通分量不是全集。

由于$n$很小,我们可以考虑状态压缩。

对于点集$S$,我们钦定一个它的子集$K$入度数为$0$,希望除去$K$以外的$S$度数不为$0$

设钦定$K$的度数为$0$其他随意的方案数为$H_{S,K}=2^{sum_S-sum_{\{S^K\}\rightarrow\{k\}}}$

设$G_S$表示$S$分为奇数个强联通分量的方案数减去分为偶数个强联通分量的方案数。

设$F_S$表示$S$的强联通生成子图数。

$G_S=-\sum\limits_{K\subset S}F_{S-K}\cdot G_K$

$F_S=2^{sum_S}-\sum\limits_{K\subset S}H_{S,K} G_K$

细节处理,对于每一个$S$,先计算$F_S$,最后再将$F_S$再加到$G_S$中去。

 

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define mod 1000000007
#define M 33000
#define N 20
using namespace std;
int read(){
	int nm=0,fh=1;char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
int n,m,sq[M],u,v;
int ind[M],otd[M],G[M],F[M],cnt[M],MAXN,sum[M],W[M];
int mul(int x,int y){return (LL)x*(LL)y%mod;}
int add(int x,int y){return (x+y)>=mod?x+y-mod:x+y;}
int mus(int x,int y){return (x-y)<0?x-y+mod:x-y;}
void init(int now,int sta){
	if(!now) return; init((now-1)&sta,sta);
	int dt=(now&-now);
	W[now]=add(W[now^dt],cnt[ind[dt]&sta]);
}
int main(){
	n=read(),m=read(),sq[0]=1,MAXN=(1<<n);
	for(int i=1;i<=m;i++){
	    sq[i]=add(sq[i-1],sq[i-1]),u=read()-1,v=read()-1;
	    ind[1<<v]|=(1<<u),otd[1<<u]|=(1<<v);
    }
    for(int i=1;i<MAXN;i++) cnt[i]=cnt[i>>1]+(i&1);
    for(int i=1;i<MAXN;i++){	
    	int ot=i-(i&-i),dt=(i&-i);
    	sum[i]=sum[ot]+cnt[ind[dt]&i]+cnt[otd[dt]&i]; 
		F[i]=sq[sum[i]],init(i,i);
    	for(int S=ot;S;S=((S-1)&ot)) G[i]=mus(G[i],mul(F[i^S],G[S]));
    	for(int S=i;S;S=((S-1)&i)) F[i]=mus(F[i],mul(sq[sum[i]-W[S]],G[S]));	
    	G[i]=add(G[i],F[i]);
	}
	printf("%d\n",F[MAXN-1]);
	return 0;
}

 

转载于:https://www.cnblogs.com/OYJason/p/9734436.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值