题目大意
给你一个DAG,边只从编号小的点连到大的点。给你两个棋子一个在1号一个在2号,小A(先手)和小B轮流进行操作:把一个棋子沿着边(x,y)从x移到y。不能操作者输。问原图有多少个子图满足小A必胜。 n<=15
解题思路
首先辨认出是个组合游戏,那么考虑使用SG函数。 我们想要知道有多少种方案sg[1]!=sg[2]。那么我们可以统计sg[1]=sg[2]的然后再减掉。 一种可行的暴力是我把点按sg值分层,然后逐层dp,这样的复杂度稍微有点大。 考虑另一种dp。我们设f[s]表示在考虑了集合s这些点后,保证sg[1]=sg[2]的方案数。(可以没有12,那就相当于12没有边) 我们按照分层的思路,看看能不能在s的底部再垫一层点,这样s的所有点sg值都会变大1。 这是可行的,于是我们子集dp,考虑s的子集t,s-t的点都要向t连至少一条边,s-t的点之间可以随便连,t的点可以和s-t随便连,但是不能连回t。那么这样就可以转移了,第一、三部分方案数可以预处理一个数组,第二部分就是f[s-t]了。 注意枚举子集的时候1和2不能属于不同层。 时间复杂度O(n*3^n)
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<map>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
const int N=
1e6+
5,M=
2e6+
5,mo=
1e9+
7;
int n,m,x,y,ts,s1,s,ns,p,ans,f[
1<<
16],cnt[
16][
1<<
16],i;
ll inc;
int tt,b[
1000],nxt[
1000],fst[
1000];
void cr(
int x,
int y)
{
tt++;
b[tt]=y;
nxt[tt]=fst[x];
fst[x]=tt;
}
int main()
{
freopen(
"t6.in",
"r",stdin);
scanf(
"%d %d",&n,&m);
fo(i,
1,m)
{
scanf(
"%d %d",&x,&y);
cr(x,y);
}
ts=
1<<n;ts--;
fo(i,
1,n) fo(s,
0,ts)
for(p=fst[i];p;p=nxt[p])
if ((
1<<b[p]-
1)&s) cnt[i][s]++;
fo(s,
0,ts)
if ((s&
1)==((s>>
1)&
1))
{
f[s]=
1;
for(ns=(s-
1)&s;ns;ns=(ns-
1)&s)
if ((ns&
1)==((ns>>
1)&
1))
{
s1=s-ns;
inc=f[s1];
fo(i,
1,n)
if (s1&(
1<<i-
1))
(inc*=(
1<<cnt[i][ns])-
1)%=mo;
else if (ns&(
1<<i-
1))
(inc<<=cnt[i][s1])%=mo;
f[s]=(f[s]+inc)%mo;
}
}
ans=
1;
fo(i,
1,m) ans=ans*
2%mo;
ans-=f[ts];
ans=(ans+mo)%mo;
printf(
"%d",ans);
}