期望得分:76+80+30=186
实际得分:72+10+0=82
先看第一问:
本题不是求方案数,所以我们不关心 选的数是什么以及的选的顺序
只关心选了某个数后,对当前gcd的影响
预处理
cnt[i] 表示 i的倍数有多少个
g[i][j] 表示gcd(i,第j张卡片上的数)
dp[i][j] 表示已经选了i个数,gcd=j 的 概率
再选k,要么gcd不变,要么变小
1、gcd不变
即k是j的倍数,因为已经选了i个且都是j的倍数,所以在剩下的n-i 个数中,还有 cnt[j]-i 个数可以选
所以状态转移方程:dp[i+1][j]+=dp[i][j]*(cnt[j]-i)/(n-i)
2、gcd变小
枚举要选的是第h个数 ,h满足gcd(a[h],j)!=j
(a[h] 表示第h张卡片上的数)
那么gcd会变为g[j][h]
因为 当gcd=1 的时候游戏结束,即 gcd=1 不能用来转移
所以 当gcd=1时,直接累计进答案,不更新dp
所以状态转移方程:dp[i+1][g[j][h]+=dp[i][j]/(n-i),g[j][h]!=1
答案的累计:
1、dp 过程中 gcd=1
只有 选了偶数个数之后,gcd=1,先手才赢
所以 在dp过程中,若i是奇数,ans+=dp[i][j]/(n-i)
(因为是在由i推出去的时候 累计答案,所以i是奇数)
2、dp完之后,没有牌选了
若n是奇数,则先手胜
所以若n是奇数,ans+=dp[n][i]
第二问:
就是裸地SG函数
sg[i][j] 表示 已经选了i个数,gcd=j 是必胜态(1)还是必败态(0)
根据
必胜态的后继状态至少有一个是必败态
必败态的后继状态全是必胜态
用 & 运算符可以方便的记录
记忆化搜索
边界:sg[n][i]=0,sg[i][1]=1
因为 选了n个数且j!=1 之后,对方败
当gcd=1 之后,对方胜
为什么要用对方的状态?(以下可能表述不清)
因为边界是在dfs 最前面判断的,而且是从选了0张牌开始
己方选了x张牌之后的状态,随dfs到了下一层里,即到了对方选的哪儿
如果己方选了n张牌且gcd!=1,己方赢,但sg[n][]的状态是到下一层dfs里判断的
主客交换,对方输,所以sg[n][]=0
sg[i][1] 同理
#include<cstdio> #include<cstring> #include<algorithm> #define N 301 #define K 1001 using namespace std; const double eps=1e-8; int n,m,a[N]; int cnt[K],g[K][N]; double dp[N][K]; int sg[N][K]; int getgcd(int a,int b) { return !b ? a : getgcd(b,a%b); } void init() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),m=max(m,a[i]); } void pre() { for(int i=1;i<=n;i++) g[0][i]=a[i]; for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) cnt[i]+=(a[j]%i==0),g[i][j]=getgcd(i,a[j]); } void getprobability() { double ans=0.0; dp[0][0]=1.0; for(int i=0;i<n;i++) for(int j=0;j<=m;j++) if(dp[i][j]>eps) { dp[i+1][j]+=dp[i][j]*(cnt[j]-i)/(n-i); for(int k=1;k<=n;k++) if(g[j][k]!=j) { if(g[j][k]!=1) dp[i+1][g[j][k]]+=dp[i][j]/(n-i); else ans+=(i&1)*dp[i][j]/(n-i); } } if(n&1) for(int i=0;i<=m;i++) ans+=dp[n][i]; printf("%.9lf",ans); } int dfs(int x,int gcd) { if(sg[x][gcd]!=-1) return sg[x][gcd]; bool win=true; if(cnt[gcd]>x) win&=dfs(x+1,gcd); for(int i=1;i<=n;i++) if(g[gcd][i]!=gcd) win&=dfs(x+1,g[gcd][i]); return sg[x][gcd]=!win; } void getsg() { memset(sg,-1,sizeof(sg)); for(int i=0;i<=m;i++) sg[n][i]=0; for(int i=0;i<=n;i++) sg[i][1]=1; if(dfs(0,0)) printf("1.000000000"); else printf("0.000000000"); } int main() { freopen("cards.in","r",stdin); freopen("cards.out","w",stdout); init(); pre(); getprobability(); printf(" "); getsg(); }