前言
经小提示做出来的DP,因为题解没看懂,淦
题目
题目大意:
有(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;
}