1. 程式人生 > >紙牌遊戲

紙牌遊戲

題目描述

華華和秀秀在玩紙牌遊戲,遊戲的規則如下:

初始時,桌面上有n張紙牌,每張紙牌上寫有一個正整數。遊戲開始時華華先在黑板上寫上數字0,之後秀秀和華華輪流選取紙牌(秀秀先手)。當一個人選定一張紙牌時,他需要將黑板上的數字改寫成這個數和紙牌上的數的最大公約數,然後將這張紙牌丟棄。當一個人寫下了1 或者無法選取紙牌時,他就輸了。

現在秀秀想知道:

  1. 當華華和秀秀都按照隨機策略選取卡片時,秀秀獲勝的概率有多少;

  2. 當華華和秀秀都按照最優策略選取卡片時,秀秀獲勝的概率有多少。

題解

兩個問題是完全沒關係的兩道題。
對於前者,我們可以記取了 i

i 張牌當前黑板上東西是 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)

j j 變成了 g c d ( j , a [ k ] ) gcd(j,a[k]) k k 列舉一下暴力轉移就行了,寫的時候主動轉移更方便。統計答案如果下一次 j j 變成了 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;
}