CF540D Bad Luck Island(概率dp)

洛谷链接:

Bad Luck Island - 洛谷

思路:

我们用一个数对( i,j,k)来表示一个状态,i是r被吃个数,j是s被吃个数,k是p被吃个数。

每个状态都有一个对应的概率(本轮次取到这种情况的概率),比如(0,0,0)时,游戏进行了0轮,只有这一种状态,所以它概率是1,而第1轮有三个状态,(1,0,0),(0,1,0)和(0,0,1),他们概率总和是1,这三个概率可以通过(0,0,0)状态时各方剩余人数计算得到。

这样,我们就可以构建出一个三叉树结构,(i,j,k)的子结点有(i+1,j,k),(i,j+1,k)和(i,j,k+1)三个。树结构不断往下延伸,每个点的概率都可以计算出来。延伸到什么时候停止呢?当所有叶子节点都满足 i=r,j=s,k=p中的两个的时候停止,因为此时游戏就结束了。

那是不是说就可以用dfs解决啦?不行!因为这样会带来大量的重复计算,比如(1,0,0)和(0,1,0)都有(1,1,0)这个子结点,他们代表相同的状态,但是dfs计算的时候却是分开计算的,两个(1,1,0)点下面的子树就被重复计算了。所以我们应该把这些相同的点“接到一起”,所以要用到动态规划,把相同结点的结果【累加】到dp数组的同一个位置,这样就能避免重复计算啦。

具体细节见代码和注释。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
double dp[maxn][maxn][maxn]; //dp[i][j][k]表示r,s,p分别被吃了i,j,k个的概率
double ansr=0, anss=0, ansp=0;
int r, s, p;

int main(){
    cin >> r >> s >> p; //r吃s,s吃p,p吃r
    dp[0][0][0] = 1.0;
    for(int i=0; i<=r; i++){
        for(int j=0; j<=s; j++){
            for(int k=0; k<=p; k++){
                int a=r-i, b=s-j, c=p-k; //r,s,p剩余人数
                int tot = c*a+a*b+b*c; //概率分母
                //当至少有两个种族存活的时候,可以进行递推(只剩一个种族,则递推必定停止)
                //每一个状态都可能由多条路线递推过来,这些概率要累加,所以是+=
                if(c&&a) dp[i+1][j][k] += dp[i][j][k]*(double)c*a/tot;
                if(a&&b) dp[i][j+1][k] += dp[i][j][k]*(double)a*b/tot;
                if(b&&c) dp[i][j][k+1] += dp[i][j][k]*(double)b*c/tot;
                //只剩一个种族就记录结果(注意,有多种情况导致只剩这一种族,所以是+=)
                if(!b&&!c) ansr += dp[i][j][k];
                if(!a&&!c) anss += dp[i][j][k];
                if(!a&&!b) ansp += dp[i][j][k];
            }
        }
    }
    printf("%.12f %.12f %.12f\n", ansr, anss, ansp);
}

update 2022/4/18

做法1:普通的概率dp

#include <bits/stdc++.h>
#define debug cout<<"!!!"<<endl;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
#define pii pair<int,int>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N = 105;
int a,b,c;
double f[N][N][N],x,y,z;

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>a>>b>>c; f[a][b][c] = 1;
    ROF(i,a,0) ROF(j,b,0) ROF(k,c,0){
        //1.统计答案
        if(j==0 && k==0 && i!=0) x += f[i][j][k];
        if(i==0 && k==0 && j!=0) y += f[i][j][k];
        if(i==0 && j==0 && k!=0) z += f[i][j][k];

        //2.考虑递推
        int tot = i*j+i*k+j*k;
        if(tot==0) continue; //如果tot是0,那递推就没有意义
        if(i>0) f[i-1][j][k] += f[i][j][k] * i*k/tot; //r被p吃
        if(j>0) f[i][j-1][k] += f[i][j][k] * i*j/tot; //s被r吃
        if(k>0) f[i][j][k-1] += f[i][j][k] * j*k/tot; //p被s吃
    }
    cout<<fixed<<setprecision(12);
    cout<<x<<' '<<y<<' '<<z;
}

做法2:记忆化dfs式 概率dp

想法很容易理解,但是边界条件很多,写起来有些繁琐。

#include <bits/stdc++.h>
#define debug cout<<"!!!"<<endl;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
#define pii pair<int,int>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N = 105;
int a,b,c;
double f[N][N][N],x,y,z;

double dfs(int i,int j,int k){
    double& dp = f[i][j][k];
    if(dp) return dp;
    if(i<a && !(j==0 && k==0)) dp += dfs(i+1,j,k)*(i+1)*k/((i+1)*j+(i+1)*k+j*k);
    if(j<b && !(i==0 && k==0)) dp += dfs(i,j+1,k)*(j+1)*i/((j+1)*i+(j+1)*k+i*k);
    if(k<c && !(i==0 && j==0)) dp += dfs(i,j,k+1)*(k+1)*j/((k+1)*i+(k+1)*j+i*j);
    return dp;
}
signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin>>a>>b>>c; f[a][b][c] = 1;
    FOR(i,1,a) x+=dfs(i,0,0);
    FOR(j,1,b) y+=dfs(0,j,0);
    FOR(k,1,c) z+=dfs(0,0,k);
    cout<<fixed<<setprecision(12);
    cout<<x<<' '<<y<<' '<<z;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值