AtCoder Grand Contest 016F Games on DAG 状压dp

Description


给定一张n个点m条有向边的dag,保证每条边x 现在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 
#include 
#include 
#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;
}

你可能感兴趣的:(c++,AtCoder,状压dp)