bzoj3812 主旋律 状压dp+容斥

28 篇文章 0 订阅
21 篇文章 0 订阅

Description


响应主旋律的号召,大家决定让这个班级充满爱,现在班级里面有 n 个男生。
如果 a 爱着 b,那么就相当于 a 和 b 之间有一条 a→b 的有向边。如果这 n 个点的图是强联通的,那么就认为这个班级是充满爱的。
不幸的是,有一些不好的事情发生了,现在每一条边都可能被摧毁。我作为爱的使者,想知道有多少种摧毁的方式,使得这个班级任然充满爱呢?(说人话就是有多少边的子集删去之后整个图仍然强联通。)
n ≤ 15 n\le15 n15

Solution


题意无力吐槽。。
容易想到设f[S]表示S这个点集合选出来组成强连通图的方案数,我们可以容斥减去不合法的方案
具体说就是减去至少奇数个强连通分量的方案数加上至少偶数个强连通分量的方案数,那么设g[S]表示S组成奇数个强连通分量的方案数,h[S]表示偶数个,那么转移有 g [ S ] = ∑ T ⊂ S h [ T ] × f [ S − T ] g[S]=\sum_{T\subset S}{h[T]\times f[S-T]} g[S]=TSh[T]×f[ST],偶数的话同理

于是直接做就可以做到 O ( n 3 n ) O(n3^n) O(n3n)了,预处理一下就能 O ( 3 n ) O(3^n) O(3n)

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define lowbit(x) (x&-x)

typedef long long LL;
const int MOD=1e9+7;
const int N=200005;

LL f[N],g[N],h[N],s[N],c[N],r[21],w[N],st[N],bin[N];

int main(void) {
	bin[0]=1; rep(i,1,N) bin[i]=bin[i-1]*2%MOD;
	freopen("data.in","r",stdin);
	int n,m; scanf("%d%d",&n,&m);
	rep(i,1,m) {
		int x,y; scanf("%d%d",&x,&y);
		r[x-1]|=(1<<y-1);
	}
	for (int i=1;i<(1<<n);++i) c[i]=(c[i>>1])+(i&1);
	for (int S=1;S<(1<<n);++S) {
		if (c[S]==1) {f[S]=g[S]=1; continue;}
		int top=0;
		for (int T=(S-1)&S;T;T=(T-1)&S) st[++top]=T;
		rep(i,0,n-1) if ((S>>i)&1) w[1<<i]=c[r[i]&S];
		drp(i,top,1) w[st[i]]=w[st[i]-lowbit(st[i])]+w[lowbit(st[i])];
		w[S]=w[S-lowbit(S)]+w[lowbit(S)]; f[S]=bin[w[S]];
		rep(i,1,top) {
			int T=st[i];
			f[S]=(f[S]+(h[T]-g[T]+MOD)*bin[w[S-T]]%MOD)%MOD;
			if (lowbit(S)!=lowbit(T)) continue;
			g[S]=(g[S]+f[T]*h[S-T]%MOD)%MOD;
			h[S]=(h[S]+f[T]*g[S-T]%MOD)%MOD;
		}
		f[S]=(f[S]+h[S]-g[S]+MOD)%MOD;
		g[S]=(g[S]+f[S])%MOD;
		rep(i,1,top) w[st[i]]=0; w[S]=0;
	}
	printf("%lld\n", f[(1<<n)-1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值