题目描述
华华和秀秀在玩纸牌游戏,游戏的规则如下:
初始时,桌面上有n张纸牌,每张纸牌上写有一个正整数。游戏开始时华华先在黑板上写上数字0,之后秀秀和华华轮流选取纸牌(秀秀先手)。当一个人选定一张纸牌时,他需要将黑板上的数字改写成这个数和纸牌上的数的最大公约数,然后将这张纸牌丢弃。当一个人写下了1 或者无法选取纸牌时,他就输了。
现在秀秀想知道:
-
当华华和秀秀都按照随机策略选取卡片时,秀秀获胜的概率有多少;
-
当华华和秀秀都按照最优策略选取卡片时,秀秀获胜的概率有多少。
题解
两个问题是完全没关系的两道题。
对于前者,我们可以记取了
i
i
i张牌当前黑板上东西是
j
j
j的概率。
若
j
j
j没有变,说明你又取了一个
j
j
j的倍数则
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
∗
(
s
u
m
[
j
]
−
i
+
1
)
/
(
n
−
i
+
1
)
f[i][j]=f[i-1][j]*(sum[j]-i+1)/(n-i+1)
f[i][j]=f[i−1][j]∗(sum[j]−i+1)/(n−i+1)
若
j
j
j变成了
g
c
d
(
j
,
a
[
k
]
)
gcd(j,a[k])
gcd(j,a[k]),
k
k
k枚举一下暴力转移就行了,写的时候主动转移更方便。统计答案如果下一次
j
j
j变成了
1
1
1则你赢。再判一下最后没东西取的情况就可以了。
对于后一个问题,orzszm,可以看看她的题解orzszm
代码
#include <bits/stdc++.h>
#define maxn 1005
#define INF 0x3f3f3f3f
using namespace std;
int read(){
int res,f=1; char c;
while(!isdigit(c=getchar())) if(c=='-') f=-1; res=(c^48);
while(isdigit(c=getchar())) res=(res<<3)+(res<<1)+(c^48);
return res*f;
}
int n,num[maxn],gcd[maxn][maxn],sum[maxn],tot[maxn],mx;
double f[maxn][maxn],ans;
bool vis[maxn];
int _gcd(int a,int b){return b?_gcd(b,a%b):a;}
int main(){
n=read();
for(int i=1;i<=n;i++){
num[i]=read(),mx=max(mx,num[i]);
for(int j=1;j<=num[i];j++){
if(num[i]%j==0) sum[j]++;
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=mx;j++){
gcd[i][j]=_gcd(num[i],j);
}
}
f[0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=mx;j++){
if(j^1) f[i+1][j]+=f[i][j]*(sum[j]-i)/(n-i);
for(int k=1;k<=n;k++){
if(gcd[k][j]^j) f[i+1][gcd[k][j]]+=f[i][j]/(n-i);
}
}
if(i&1) ans+=f[i+1][1];
}
if(n&1) for(int i=2;i<=mx;i++) ans+=f[n][i];
printf("%.9lf ",ans);
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
vis[gcd[i][num[j]]]=1;
}
}
for(int i=2;i<=mx;i++){
if(vis[i]) for(int j=2*i;j<=mx;j+=i) vis[j]=0;
}
for(int i=2;i<=mx;i++){
if(vis[i]) for(int j=1;j<=n;j++) tot[i]+=(num[j]%i==0);
}
vis[1]=0;
for(int i=1;i<=n;i++){
bool flag=1;
for(int j=1;j*j<=num[i];j++){
if(num[i]%j==0){
if(vis[j]) flag&=tot[j]&1;
if(vis[num[i]/j]) flag&=tot[num[i]/j]&1;
}
}
if(flag){puts("1.000000000"); return 0;}
}
puts("0.000000000");
return 0;
}