ED: Gem Island 题解
题目大意
- 有
n 个人,初始时每个人手上有一颗宝石。 - 在
d 天内,每天会等概率随机一颗宝石,将其分裂为两块宝石。 - 求
d 天后,宝石数前r 多的人手上宝石数的期望,要求误差不超过10^{-6} 。 -
题目解法
直接冲上去 dp 怎么看都不是很好做,考虑挖掘一点性质。
先看看对于一种最终状态
第一步是将
第二步是对于每一天,计算当天被钦定的人手上宝石数增加的方案数;事实上,在一个人手上的宝石第
将两个部分乘起来,则我们可以得到一个令人兴奋的结论:对于任何一种最终状态,它发生的方案数都是
那么我们就可以直接 dp 所有方案数以及这些方案的前
考虑一种经典的“搭楼梯”的 dp 方法:
我们维护类似这样一个“楼梯”状的东西:
***
******
******
********
********
********
**********
**********
考虑设 * 的总数为
转移很简单,考虑在最高层加入一行,枚举这行里头有 *,然后从现有的 * 就是一种转移了。比如对上面的
** <--- 新增的行
***
******
******
********
********
********
**********
**********
将这个东西放到这个题中,我们可以将宝石对应成 *,每一列对应一个人就行了。
然后考虑计算所有方案的前 * 的和。事实上也很简单,由于我们每次加入一行的那些列,一定就是前 *,所以我们可以直接计算贡献。具体来说,我们类比
最后答案也很显然,就是
了。
由于此题比较神必,所以全上 double 就行了。
Code:(实际写的转移可能和上面叙述的有一点点小区别,本质一样)
typedef long long ll;
typedef double db;
db f[1010][1010];
db g[1010][1010];
db C[1010][1010];
int main() {
int n = ri, d = ri, r = ri;
C[0][0] = 1;
for(int i = 1; i <= n; i++) {
C[i][0] = 1;
for(int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
f[0][n] = 1;
for(int i = 0; i < d; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= j; k++) {
f[i + k][k] += f[i][j] * C[j][k];
g[i + k][k] += (g[i][j] + min(r, k) * f[i][j]) * C[j][k];
}
db G = 0, F = 0;
for(int i = 1; i <= n; i++)
G += g[d][i], F += f[d][i];
printf("%.8lf\n", G / F + r);
return 0;
}