期望得分:76+80+30=186

实际得分:72+10+0=82

 

2017 国庆湖南 Day5

先看第一问:

 

本题不是求方案数,所以我们不关心 选的数是什么以及的选的顺序

只关心选了某个数后,对当前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();
}
View Code

相关文章: