Games on DAG
Problem Statement
给出一个有n个点的
现在有两个棋子,一个在1,一个在
每次可以把一个棋子往它所在点连向的点中的一个移动,不能操作者算输。 两人绝顶 聪明,问有多少种边集的子集满足当只保留这子集中的边时
Data Constraint
2≤
Solution
比较容易想的的就是当边集的子集确定时,1和
正难则反,我们用所有的方案数减去不合法的方案数。
按照mex分层进行状压dp。
设fS表示已经选了点的二进制状态为S时的方案数。
接下来我们枚举
为了保证T中的点的
为了保证S′中的点的SG值不为0,
接着
这样f′S就能转移到fS了。
理解一下就是枚举一个S的真子集
那如何保证1和
时间复杂度
Code
#include<bits/stdc++.h>
#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)
using namespace std;
typedef long long ll;
const ll N=20,K=N*N,M=1e5,mo=1e9+7;
int d[N],g[N],bit[M];
int r[N];
int n,m,j,k,l,i;
ll f[M],way[K];
inline int read()
{
int o=0; char ch=' ';
for(;ch<'0'||ch>'9';ch=getchar());
for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
return o;
}
int main()
{
cin>>n>>m;
d[1]=1; fo(i,2,n+1)d[i]=d[i-1]<<1;
way[0]=1;
fo(i,1,m+10)way[i]=(way[i-1]<<1)%mo;
int T=d[n+1]-1;
fo(i,1,T)bit[i]=bit[i>>1]+(i&1);
fo(i,1,m){
int x=read(),y=read();
g[x]|=d[y];
}
f[0]=1;
fo(i,3,T)if((i&1)==((i&2)>>1)){
int u=0;
fo(l,1,n)if(i&d[l])r[++u]=l;
fo(l,0,d[u+1]-2){
int o=0;
fo(j,1,u)(l&d[j])?o|=d[r[j]]:0;
if((o&1)!=((o&2)>>1))continue;
int p=i^o,ok=0;
ll choose=1;
fo(j,1,n)if((o&d[j])>0&&(g[j]&p)==0){
ok=1; break;
}else (o&d[j])?choose=choose*(way[bit[g[j]&p]]-1)%mo:0;
if(ok)continue;
fo(j,1,n)(p&d[j])?choose=choose*way[bit[g[j]&o]]%mo:0;
f[i]=(f[i]+choose*f[o])%mo;
}
}
ll ans=(way[m]-f[T]+mo)%mo;
cout<<ans;
}