【agc016

xiaoxiao2021-02-28  63

题目大意

给你一个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); //freopen("t6.out","w",stdout); 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); }
转载请注明原文地址: https://www.6miu.com/read-2629866.html

最新回复(0)