BZOJ3812 清华集训2014 主旋律

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

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

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

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

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

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

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

$G_S=-sumlimits_{Ksubset S}F_{S-K}cdot G_K$

$F_S=2^{sum_S}-sumlimits_{Ksubset 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
",F[MAXN-1]);
	return 0;
}
原文地址:https://www.cnblogs.com/OYJason/p/9734436.html