【AGC016F】Games on DAG

题目

题目链接:https://atcoder.jp/contests/agc016/tasks/agc016_f
给定一个 \(n\) 个点 \(m\) 条边的 DAG,对于每条边 \((u,v)\) 都满足 \(u<v\)\(1,2\) 号点各一个石头,每次可以沿 DAG 上的边移动一颗石头,不能移动则输,求所有 \(2^{m}\) 个边的子集中,只保留这个子集先手必胜的方案个数。
\(n\leq 15\)

思路

因为石头是独立的,所以也就是求有多少中方案使得 \(\text{SG}(1)\neq \text{SG}(2)\)。考虑求出 \(\text{SG}(1)=\text{SG}(2)\) 的方案数再用 \(2^m\) 减一下。
对于一个点集 \(S\),考虑如何从它的子集 \(T\) 转移而来。我们可以把 \(U=S \setminus T\) 的点看作 \(\text{SG}=0\) 的点,\(T\) 里面的点看作 \(\text{SG}>0\) 的点,那么对于 \(x\in T\)\(\text{SG}(x)\) 应该是 \(x\) 连向的点的 \(\text{SG}\)\(\text{mex}\)。也就是 \(x\) 至少要向 \(U\) 中的一个点连边。
再考虑 \(y\in U\) 的连边,为了让 \(\text{SG}(y)=0\),那么 \(y\) 不可以向 \(U\) 中任何一个点连边,而其他 \(S\) 中的点可以任意连。
\(f[S]\) 表示点集为 \(S\) 的时候,\(\text{SG}(1)=\text{SG}(2)\) 的方案数,那么枚举 \(S\) 的子集 \(T\),再枚举 \(S\) 中所有点,设它们的连边方案数之积为 \(s\),那么 \(f[S]\gets f[T]\times s\)。因为可以看作把 \(T\) 中所有点的 \(\text{SG}\) 都加了 \(1\)\(T\) 中连边方案依然是 \(f[T]\)
时间复杂度 \(O(3^nn)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=16,M=(1<<15),MOD=1e9+7;
int n,m,lim,f[M],S[N],bit[M];

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		S[x]|=(1<<y-1);
	}
	lim=(1<<n);
	for (int i=1;i<lim;i++)
		bit[i]=bit[i>>1]+(i&1);
	for (int i=1;i<lim;i++)
		if ((i&1)==((i>>1)&1))
		{
			f[i]=1;
			for (int j=i&(i-1);j;j=(j-1)&i)
				if ((j&1)==((j>>1)&1))
				{
					int s=f[j];
					for (int k=1;k<=n;k++)	
					{
						if (j&(1<<k-1)) s=1LL*s*((1<<bit[(i^j)&S[k]])-1)%MOD;
						if ((i^j)&(1<<k-1)) s=1LL*s*(1<<bit[j&S[k]])%MOD;
					}
					f[i]=(f[i]+s)%MOD;
				}
		}
	int s=1;
	while (m--) s=(s+s)%MOD;
	cout<<(s-f[lim-1]+MOD)%MOD;
	return 0;
}
posted @ 2021-05-31 15:36  stoorz  阅读(82)  评论(0编辑  收藏  举报