卡牌遊戲(題解)
TempestMiku · · 题解
P2059题目传送门
思路设计
按照一般思路,
但是这道题是约瑟夫环的基础上进行游戏,每轮游戏完之后会踢出一个人,所以每轮游戏的人数是不一样的。
先考虑这样一个关键的问题,这道题是顺推还是逆推?
-
假设是顺推,我们需要从第一场游戏(即剩下
n 个人)推到第n-1 场比赛(即剩下1 个人),显然你不知道最开始每个人赢的概率,所以不能顺推。 -
相反的,如果是逆推,我们需要从第
n-1 场游戏(即剩下1 个人)推到第1 场比赛(即剩下n 个人),总共进行n-1 场游戏。
于是我们可以用
可能有人会问:为什么不用进行到第
-
因为剩下
i 个人就可以表示出来进行到第几轮游戏了(每轮游戏踢出一个人)。 -
如果是编号为
j 的人的获胜概率,那么你的初始状态可能无法设计,假设只剩下1 个人了,每个人都有一定的概率获胜,而分别获胜的概率又是根据随机抽牌决定的。所以初始状态难以设计。
动态规划转移
设
可以看出这里
按照套路,先找出转移边界:
然后第一层循环从
根据约瑟夫环,根据抽到的牌和这轮的人数算出这轮踢出的人。
算出来剩下
然后通过剩下
然后根据这两轮庄家后相同的人转移:剩下
如图,左边是剩下
设
最后放上代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace Testify{
inline int read(){
int f(1),x(0);
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f*x;
}
inline void Write(int x){
if(x>9) Write(x/10);
putchar(x%10+48);
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
Write(x);
putchar(' ');
}
}
using namespace Testify;
const int N=1e5+5;
int n,m;
double dp[55][55];//剩下i个人,赢的人从庄家 数起是j的概率()
int card[55];
signed main(void){
n=read(),m=read();
for(register int i=1;i<=m;i++){
card[i]=read();
}
dp[1][1]=1.0;//只剩下1个人力
for(register int i=2;i<=n;i++){//
for(register int j=1;j<=m;j++){
int p=card[j]%i;
if(p==0) p=i;
for(register int k=1;k<i;k++){//上一个状态
p++;
if(p>i) p=1;
dp[i][p]+=(dp[i-1][k]/m);
}
}
}
for(register int i=1;i<n;i++){
printf("%.2lf%% ",dp[n][i]*100);
}
printf("%.2lf%%",dp[n][n]*100);
return 0;
}