【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;
}