BZOJ 3812: 主旋律 状压dp 容斥原理

3812: 主旋律

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 332  Solved: 269
[Submit][Status][Discuss]

Description

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

Input

第一行两个数 n 和 m,表示班级里的男生数和爱的关系数。
接下来 m 行,每行两个数 a 和 b,表示男生 a 爱着男生 b。同时 a 不等于 b。
所有男生从 1 到 n 标号。
同一条边不会出现两遍,但可能出现 a 爱着 b,b 也爱着 a 的情况,这是两条不同的边。

Output

输出一行一个整数,表示对 109+7 取模后的答案。

Sample Input

5 15
4 3
4 2
2 5
2 1
1 2
5 1
3 2
4 1
1 4
5 4
3 4
5 3
2 3
1 5
3 1

Sample Output

9390

HINT

对于 100% 的数据满足: n≤15,0≤m≤n(n−1)。


这是一道好题

运用正难则反的思想,考虑补集转化,求整个图不强连通的方案数

那么若整个图不强连通,则缩点之后一定为一个拓扑图的集合

枚举没有出度的scc的集合 由其他点与之相连 则会构成DAG

这时考虑固定的集合若有奇数个scc,则容斥系数为-1,偶数为1

令 f[i] 表示 i 状态下合法方案数,g[i] 表示 i 状态下奇数个scc的方案数,h[i] 表示偶数个scc的方案数

则可以得到转移




#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef long long ll;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=10*x+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=16,mod=int(1e9)+7;

int n,m;

int f[(1<<N)+100],g[(1<<N)+100],h[(1<<N)+100];
// f the whole graph is a scc
// g the number of scc is odd
// h the number of scc is even

int pw[N*N],bit[(1<<N)+100],w[(1<<N)+100][2];

int in[(1<<N)+100],out[(1<<N)+100];

int main()
{
	n=read(),m=read();
	register int i,j,u,v,tmp;
	for(i=1;i<=m;++i)
		u=read(),v=read(),
		out[1<<(u-1)]|=1<<(v-1),in[1<<(v-1)]|=1<<(u-1);
	
	for(i=1;i<(1<<n);++i) bit[i]=bit[i>>1]+(i&1);
	pw[0]=1;for(i=1;i<=m;++i) pw[i]=(pw[i-1]<<1)%mod;
	
	h[0]=1;
	for(i=1;i<(1<<n);++i)
	{
		for(j=i;j;j-=(j&-j))
			w[i][0]+=bit[out[j&-j]&i];
		
		if(i==(i&-i))
		{f[i]=g[i]=1;continue;}
		
		f[i]=pw[w[i][0]];
		for(j=i&(i-1);j;j=i&(j-1))
			if(j&(i&-i))
				(g[i]+=1ll*f[j]*h[i^j]%mod)%=mod,
				(h[i]+=1ll*f[j]*g[i^j]%mod)%=mod;
		
		for(j=i&(i-1);j;j=i&(j-1))
		{
			tmp=(i^j)&-(i^j),
			w[j][1]=w[j^tmp][1]-bit[in[tmp]&(i^j^tmp)]+bit[out[tmp]&(j)];
			
			(f[i]+=1ll*(h[j]-g[j])*pw[w[i^j][0]+w[j][1]]%mod)%=mod;
		}
		(f[i]+=(h[i]-g[i])%mod)%=mod;
		if(f[i]<0) f[i]+=mod;
		(g[i]+=f[i])%=mod;
	}
	cout<<f[(1<<n)-1]<<endl;
	return 0;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值