AGC 016 F - Games on DAG(状压dp)
题意
给你一个有 个点
条边 DAG 图,点的标号和拓扑序一致。
现在有两个人进行博弈,有两个棋子分别在 号点上,需要不断移动到它指向的点上。
如果当前两个点都无法移动,那么就视为当前操作的人失败。
问有多少边集满足先手必胜。
这是一道思维非常困难的状压题
我们考虑转化问题,两个棋子是独立的,那么我们如果求出了这两个棋子的SG值
最后就是求两个棋子SG值不相同的方案数
我们考虑补集转化,现在要求两个棋子SG值相同的方案数
dp[s]表示选的点的状态为s时,总共的方案数
我们要让1,2个点sg为0,所以要么一起选1,2,要么一起不选
然后我们可以枚举子集ss,子集的sg全为0,x^ss全部不为0
那么把x^ss全都连到ss上面,他们的sg函数全部加1
这样无论何时1,2两个点就都是sg函数相同的了
最后dp[(1<<n)]就是答案了
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,m,shu1,shu2,point[1000005],dp[1000005];
int ans,num[1000005];
int moc(int x)
{
if(x>=mod) return x-mod;
if(x<0) return x+mod;
return x;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&shu1,&shu2);
shu1--,shu2--;
point[shu1]|=1<<shu2;
}
int maxa=(1<<n)-1;
dp[0]=1;
for(int i=0;i<=maxa;i++)
{
int now=0;
for(int j=0;j<n;j++)
if(i>>j&1) now++;
num[i]=1<<now;
}
for(int s=1;s<=maxa;s++)
if((s&1)==(s>>1&1))
{
for(int ss=s;ss;ss=(ss-1)&s)
if((ss&1)==(ss>>1&1))
{
int t=s^ss,now=1;
for(int i=0;i<n;i++)
if(ss>>i&1)
now=1ll*now*num[point[i]&t]%mod;
else if(t>>i&1)now=1ll*now*(num[point[i]&ss]-1)%mod;
dp[s]=moc(dp[s]+1ll*now*dp[t]%mod);
}
}
ans=1;
for(int i=1;i<=m;i++)
ans=moc(ans<<1);
ans=moc(ans-dp[maxa]);
printf("%d",ans);
return 0;
}