【AtCoder Grand Contest 016 F】Games on DAG
状压 DP,博弈论。
题目大意:
给定一张有向无环图,一开始有两个棋子在 $1$ 和 $2$ 结点上。
两人轮流行动,每次可以选择一个棋子,并走过一条边。不能行动者输。
问这张图有多少张子图,能使先手必胜。
题解:
妙。
先手必胜的条件是 $1$ 和 $2$ 的 sg 值不相等。
我们考虑求 $1$ 和 $2$ 的 sg 值相等的方案数。
考虑状压 DP,令 $f_S$ 表示点集 $S$ 中的元素,满足 $1$ 和 $2$ 的 sg 值相等的方案数。
所以 $S$ 首先一定包含 $1$ 和 $2$。
我们考虑枚举 $S$ 的子集 $U$,表示 sg 值为 $0$ 的部分,其补集 $T$ 为 sg 值不为 $0$ 的部分。
考虑它们之间的连边:
- $U$ 内部不能连边,否则不能保证 sg 值为 $0$。
- $T$ 的每个点必须至少有一条路径到 $U$ 中的一个点,保证是必胜态。
- $U$ 到 $T$ 任意连边。
- $T$ 内部的连边的总数就是 $f_T$。因为 $T$ 中所有点都向 sg 值为 $0$ 的点连边,相当于 $T$ 里每个点的 sg 值整体都加了 $1$。所以是不为 $0$ 的方案。
$U$ 和 $T$ 也要保证 $1$ 和 $2$ 的 sg 值一样,所以要么同时包含 $1$ 和 $2$,要么同时不含。
这样就可得到先手必败的方案数。用总数减去其即可。
时间复杂度 $O(3^nn)$。
Code:
#include<cstdio>
#define popcnt __builtin_popcount
typedef long long LoveLive;
const int N=15,M=32768,md=1e9+7;
int f[M],n,m,e[N];
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<m;++i){
int u,v;
scanf("%d%d",&u,&v);
e[u-1]|=1<<(v-1);
}
const int C=1<<n;
f[0]=1;
for(int S=3;S<C;++S)if((S&1)==(S>>1&1))
for(int U=S;U;U=(U-1)&S)if((U&1)==(U>>1&1)){//sg=0
int T=S^U;
int T_U=1,U_T=1;
for(int i=0;i<n;++i){
if(U>>i&1)U_T=U_T*(1LL<<popcnt(e[i]&T))%md;else
if(T>>i&1)T_U=T_U*((1LL<<popcnt(e[i]&U))-1)%md;
}
f[S]=(f[S]+(LoveLive)U_T*T_U%md*f[T])%md;
}
int ans=1;
for(int i=1;i<=m;++i)ans=ans*2%md;
ans=(ans-f[C-1]+md)%md;
printf("%d",ans);
return 0;
}