AtCoder Grand Contest 016F Games on DAG 状压dp

36 篇文章 0 订阅
21 篇文章 0 订阅

Description


给定一张n个点m条有向边的dag,保证每条边x<y
现在A和B分别放两个棋子在1和2节点上,然后轮流移动棋子,不能动者输
问2m个子图中先手必胜的子图的方案数
n ≤ 15 n\le15 n15

Solution


这个范围一看就是状压

考虑先手必胜的含义,那么就是1和2节点的sg不相等的方案数
直接做不太好弄,我们可以补集转化算sg相等的方案数

设f[S]表示选了S这个子集的答案,其中1和2要么都在S中要么都不在
把S分成两部分a和b,其中a里面sg全为0,b中全不为0,那么a到b的边可以随便连,b中的每个点至少连向a中的一个点,a的内部一条边都不能连,b的内部连边方案恰好就是f[b]

为啥呢,我们考虑把f[b]表示的方案sg值全部加一,这样就得到了一个全不为零的子集了

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(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=18;

LL f[1<<N],g[N][1<<N],bin[155];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int main(void) {
	bin[0]=1; rep(i,1,125) bin[i]=bin[i-1]*2LL%MOD;
	int n=read(),m=read();
	rep(i,1,m) {
		int x=read()-1,y=read()-1;
		++g[x][1<<y];
	}
	rep(i,0,n-1) {
		for (int j=1;j<(1<<n);++j) {
			g[i][j]=g[i][j-lowbit(j)]+g[i][lowbit(j)];
		}
	}
	for (int S=1;S<(1<<n);++S) {
		if ((S&1)!=((S>>1)&1)) continue;
		f[S]=1;
		for (int a=S,b,w;a;a=(a-1)&S) {
			if ((a&1)!=((a>>1)&1)) continue;
			b=S-a; w=f[b];
			rep(i,0,n-1) {
				if ((b>>i)&1) w=(bin[g[i][a]]-1)*w%MOD;
				if ((a>>i)&1) w=bin[g[i][b]]*w%MOD;
			}
			f[S]=(f[S]+w)%MOD;
		}
	}
	LL ans=(bin[m]+MOD-f[(1<<n)-1])%MOD;
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值