[CF28C] Bath Queue

前言

经小提示做出来的DP,因为题解没看懂,淦

题目

洛谷

CF

题目大意:

​ 有(n(1≤n≤50))个学生同时洗衣服,有(m(1≤m≤50))个洗衣房,第(i)个洗衣房里有(a_i(1≤a_i≤50))台洗衣机。
​ 每个学生会从(m)个洗衣房里随机选择一个,选择同一个洗衣房的学生会按照房间里洗衣机的数量平均排队,求最长队列人数的期望,输出误差不大于(10^{-9})

​ 平均排队是指不存在两个队列人数相差大于二。

讲解

既然我期望学的差,就不要试图直接DP转移期望了(所以没看懂题解qwq)

所以我们DP求方案数

我们令(dp[i][j][k])为前(i)个房间去(j)个人,最长队列恰好(k)的方案数,转移为当前房间进入(l)个人,由(i-1)转移而来

考虑状态转移方程怎么写

第一维:(i-1)(i)

第二维:(j)(j+l)

第三维:(k)(max(⌈frac{l}{a_i}⌉,k))

那么(dp[i][j][k])提供的贡献是多少呢?因为人是不一样的,所以我们要从剩下的(n-j)人中选出(l)个人来,所以要乘上(C_{n-j}^{l})

所以总的状态转移方程即为:

[dp[i][j+l][max(ceil(l/a[i]),k)]=sum dp[i-1][j][k]*C_{n-j}^{l} ]

由于多个(sum)会使得转移方程看起来过于冗长且没有什么太大意义,所以我省略掉了

所以总方案数为:(T=sum_{i=0}^n dp[m][n][i])

期望与带权平均差不多,而每个(dp[m][n][i])的权值即为(i)

所以答案为:

[sum_{i=1}^{n}frac{i*dp[m][n][i]}{T} ]

什么?你问我为什么(i)(1)开始?难道和从(0)开始有什么区别吗?

注意:

1.输入的(n,m)分别指人和房间数,不要搞反了

2.输出位数不要太少了

3.建议用( exttt{double})存,万一用( exttt{long long})出事了呢

代码

int n,m,a[MAXN];
double C[MAXN][MAXN],dp[MAXN][MAXN][MAXN],ans,T;
void pre()
{
	C[0][0] = 1;
	for(int i = 1;i <= 50;++ i)
	{
		C[i][0] = 1;
		for(int j = 1;j <= i;++ j) C[i][j] = C[i-1][j-1] + C[i-1][j];
	}
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	pre();
	n = Read(); m = Read();
	for(int i = 1;i <= m;++ i) a[i] = Read();
	dp[0][0][0] = 1;
	for(int i = 1;i <= m;++ i)
		for(int j = 0;j <= n;++ j)
			for(int k = 0;k <= j;++ k)
				for(int l = 0;l <= n-j;++ l)
					dp[i][j+l][Max(((l+a[i]-1)/a[i]),k)] += dp[i-1][j][k]*C[n-j][l];
	for(int i = 0;i <= n;++ i) T += dp[m][n][i];
	for(int i = 1;i <= n;++ i) ans += i * dp[m][n][i] / T;
	printf("%.10f",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/PPLPPL/p/13767302.html