Description
给定一张n个点m条有向边的dag,保证每条边x<y
现在A和B分别放两个棋子在1和2节点上,然后轮流移动棋子,不能动者输
问2m个子图中先手必胜的子图的方案数
n
≤
15
n\le15
n≤15
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 <stdio.h>
#include <string.h>
#include <algorithm>
#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;
}