Atcoder Grand Contest 016 F Games on DAG

本文探讨了一个基于有向无环图(DAG)的双人游戏问题。游戏中两名玩家轮流移动棋子,目标是找到所有让先行者必胜的边集子集。采用状态压缩动态规划与Sprague-Grundy理论解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Games on DAG

Problem Statement

给出一个有n个点的DAG,其中有m条边,第i条有向边(xi,yi)满足xi<yi
现在有两个棋子,一个在1,一个在2AB进行游戏,A先手,轮流进行。
每次可以把一个棋子往它所在点连向的点中的一个移动,不能操作者算输。 两人绝顶 聪明,问有多少种边集的子集满足当只保留这子集中的边时A必赢。

Data Constraint

2n15

Solution

比较容易想的的就是当边集的子集确定时,12就变成了两个独立的游戏,只要它们 的SG值的异或和不为0即可,也就是它们的SG值不相等即可。
正难则反,我们用所有的方案数减去不合法的方案数。
按照mex分层进行状压dp

fS表示已经选了点的二进制状态为S时的方案数。
接下来我们枚举S的一个非空子集T表示T中的点的SG值为0,设T关于S的补集为S
为了保证T中的点的SG值为0,T中的点之间不能有边相连。
为了保证S中的点的SG值不为0,S中的每一个点至少连向T中的一个点。
接着T中的点可以往S随便连边。
这样fS就能转移到fS了。
理解一下就是枚举一个S的真子集S,将S中的SG值全部加1,T中的点的SG值为0。 这样就巧妙地实现了SG值的分层了。

那如何保证12SG值相等呢?只要保证S,S,T12的状态同为0或同为1即可。

时间复杂度O(3n)

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值