2011北京区域赛概率DP改:食堂

链接:https://ac.nowcoder.com/acm/problem/210487
来源:牛客网

题目描述 
吉吉国王偶尔会回想起自己的高中时代。在吉吉国王的高中时代,下课后冲向食堂是每个学生的基本操作,但是总得有人失败,为什么不能是我,实际上吉吉国王在打饭这件事上也是失败过很多次,比如没带饭卡,走错窗口,甚至食堂关门。
吉吉国王的高中食堂排队可以看成一个长度为nn的队列,一开始吉吉国王站在mm这个位置上,一般来说,窗口前的第一个人在打饭的时候会发生四种情况。
第一种情况是打饭的时候窗口没人,这个时候要等待一会儿,发生的概率是p1 。
第二种情况是发现自己没带饭卡,这个时候就要回去拿饭卡并且排到了队列的末尾,发生的概率是p2 。(这里认为每个人只有在即将打饭的时候才会去摸饭卡,只有这时才有发现自己没带饭卡的机会。)
第三种情况是打饭成功,这个时候队列的长度减一,发生的概率是p3
第四种情况是食堂关门,这个时候大家都不能打饭了,发生的概率是p4
吉吉国王老倒霉蛋了,经常在食堂关门的时候排在队伍的前面,因此他想知道这样的事件发生的概率。现在你需要告诉吉吉国王在食堂关门时他排在队伍的前kk位的概率。
输入描述:
一行七个数表示n, m, k, p_1, p_2, p_3, p_4n,m,k,p_1 ,p_2 ,p_3 ,p_4 。
输出描述:
输出一个小数表示答案,小数点后保留五位。
示例1
输入
4 4 1 0.372818 0.318286 0.220035 0.0888615
输出
0.15428:

这道题的代码不长,但是在推公式的时候用了很多常见的套路:
定义状态f[i][j]表示一共i个人的队伍里牛牛处在位置j处的结果
首先,所有概率都是对队首的第一个人说的,因此我们可以写出状态转移方程:

if j==1 
		f[i][j] = p1*f[i][j] + p2*f[i][i] + p4
此时在满足要求的位置,因此可以等待p1,第一个人(此时为牛牛)忘带饭卡p2,食堂关门p4
if j>=2&&j<=k 
		f[i][j]=p1*f[i][j]+p2*f[i][j-1]+p3*f[i-1][j-1]+p4
此时在满足要求的位置,因此可以等待p1,第一个人忘带饭卡p2,第一个人打饭成功,食堂关门p4
if j>k
		f[i][j]=p1*f[i][j]+p2*f[i][j-1]+p3*f[i-1][j-1]
此时在满足要求的位置,因此可以等待p1,第一个人忘带饭卡p2,第一个人打饭成功

由于两边都有f[i][j],那么经过移项合并同类项可以得到:
j==1
		f[i][1]=p2/(1-p1)*f[i][i]+p4/(1-p1)
j<=k
		f[i][j]=p2/(1-p1)*f[i][j-1]+p3/(1-p1)*f[i-1][j-1]+p4/(1-p1)
j<=n
		f[i][j]=p2/(1-p1)*f[i][j-1]+p3/(1-p1)*f[i-1][j-1]

因此我们令:
a=p2/(1-p1)
b=p3/(1-p1)
c=p4/(1-p1)

观察式子发现当计算到f[i][j]的时候,f[i-1][j-1]已经被算出来了,因此我们可以将其看作常数项,继续定义化简:
j==1 
		d[j]=c
j<=k
		d[j]=b*f[i-1][j-1]+c
j<=n
		d[j]=b*f[i-1][j-1]


最后式子就被化简为了:
j==1
		f[i][1] = a*f[i][i]+d[1]
j>=2
		f[i][j]=a*f[i][i-1]+d[j]

多列几项f[i][j]可以发现从第一个式子开始全部带进最后一个式子可以算出f[i][i],因此先算出f[i][i]:
f[i][i]=(∑p[i-k]d[k])/(1-p[i])

然后再递推求出其他f[i][j]即可~
#include<bits/stdc++.h>
using namespace std;

const int N = 2010;
const double eps = 1e-9;

int n,m,k;
double p1,p2,p3,p4,f[N][N];
double p[N],d[N],a,b,c;

int main(){
    cin >> n >> m >> k >> p1 >> p2 >> p3 >> p4;
    
    if(fabs(p1-1)<eps){cout << "1.00000\n" << endl ; return 0;}
    if(p4<eps) {cout << "0.00000\n" << endl; return 0;}
    
    a = p2/(1.0-p1) , b = p3/(1.0-p1) , c = p4/(1.0-p1);
    
    p[0]=1;
    for(int i=1;i<=n;i++)
        p[i]=p[i-1]*a;
    
    f[1][1] = p4 / (1.0-p1-p2);
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i;j++){
            //先求d[j]
            if(j==1) d[j]=c;
            else if(j>=2&&j<=k) d[j]=b*f[i-1][j-1]+c;
            else d[j]=b*f[i-1][j-1];
        }
        
        //再求f[i][i]
        for(int j=1;j<=i;j++)
            f[i][i]+=d[j]*p[i-j];
        f[i][i]=(double)f[i][i]/(1.0-p[i]);
        
        //递推
        for(int j=1;j<=i;j++)
        {
            if(j==1) f[i][j]=a*f[i][i]+d[1];
            else f[i][j]=a*f[i][j-1]+d[j];
        }
    }    
    
    printf("%.5lf",f[n][m]);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值