Codechef BAKERY

这道题目巧妙的点在于按照服务员分组,考虑用 \(f[i][j][k]\) 表示在时刻 \(i\) ,当前服务员距离自己的下一个客人还有 \(j\) 个,同时此时来人时的等待时间为 \(k\) 时,该服务员已经分配到的顾客的期望等待时间。

我们如果考虑这个方法暴力做复杂度是 \(O(n^2d)\) 的,必然不能通过,需要考虑优化。

首先如果考虑顺序转移我们既需要记录期望又需要记录概率(实际上可以直接利用期望的线性性直接将每一个的期望拆开只用概率计算),两个变量不是很方便我们优化的定义,考虑倒序,定义每一个节点并不是从起点出发的期望等待时间,而是到达终点的期望等待时间,相当于整一个 dp 被我们倒过来了,可以比较简单地发现,这个时候的 dp 我们只需要记录一个期望,因为起点(也就是倒序之后的终点)只存在一个。

此时我们可以发现 \(p\) 的取值范围非常的特殊,意味着人的出现频率还是大致为 \(\frac{1}{2}\) 的,也就是说 \(d\) 的取值很大程度的决定了在 \(k\) 较大的时候的期望值的特点。

若 \(d\) 较小,我们不难想到在 \(k\) 较大的时候的出现概率是极小的,所以其对最后期望的贡献也是小的,我们可以直接忽略精度低于 \(10^{-6}\) 的不会影响正确性的期望;若 \(d\) 较大,那么当 \(k\) 在较大的时候基本必然会等待,然后如果是等待,等待时间是与 \(k\) 正相关的,我们可以将其近似地看成与 \(k\) 相关的线性函数,直接利用斜率求解。

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+5,M=10;
int n,m,d;double p,res=0;
double f[2][M][2000],g[2][M];bool flag;
double dp(int i,int j,int c){
	if(flag){if(c<2000) return f[i&1][j][c]; else return 0;}
	else{if(c<2000) return f[i&1][j][c];else return g[i&1][j]*(c-1999)+f[i&1][j][1999];}
}
int main(){
	cin>>n>>m>>d>>p,flag=(d*p<=m);
	for(int i=n;i>=1;--i){
		for(int j=0;j<m;++j) g[i&1][j]=f[i&1][j][1999]-f[i&1][j][1998];
		for(int j=0;j<m;++j){
			for(int c=0;c<2000;++c){
				// printf("%d %d %d %lf\n",i,j,c,f[i&1][j][c]);
				f[(i-1)&1][j][c]=(1-p)*dp(i,j,max(c-1,0));
				if(j) f[(i-1)&1][j][c]+=p*dp(i,j-1,max(c-1,0));
				else f[(i-1)&1][j][c]+=p*(dp(i,m-1,c+d-1)+c);
			}
		}
	}
	for(int j=0;j<m;++j) res+=f[0][j][0];
	return printf("%.10lf\n",res),0;
}
/*
考虑 d 较大和 d 较小两种情况,
*/
上一篇:P2062 分队问题 题解


下一篇:【数据结构】手撕单链表