IOI2021集训队作业 123ED Gem Island

题目相当于:有个序列\(\{a_i\}\),一开始全\(0\)。有\(d\)次操作,每次随机地选一个\(i\),概率为\(\frac{a_i}{\sum a_i}\),然后将\(a_i\)变成\(a_i+1\)

求最终前\(r\)个期望是多少。

答案输出小数。


早就想到了大概的思路,但因为答案输出小数而不是取模迟迟不敢下手。因为这题在计数过程中铁定会出现很大的数的,然后最后再除以一个很大的组合数。看起来精度问题就很可怕。

后来翻翻题解发现原来没有任何精度的问题。。

假如最后形成的有序序列为\(\{a_i\}\),得到它的概率为\(\frac{1}{\prod_{i=n}^{n+d-1}i}\prod a_i!\frac{(\sum a_i)!}{\prod a_i!}\),即\(\frac{1}{\binom{n+d-1}{n}}\)

于是只需要统计每个有序序列的贡献。转化成统计无序序列(排序后)的方案数和贡献。

有如此DP:\(f_{i,j}\)表示\(i\)个数和为\(j\)的方案数,\(g_{i,j}\)\(i\)个数和为\(j\)的前\(r\)个数的和。

转移考虑枚举\(k\)\(k\)个至少为\(1\)\(i-k\)个恰好为\(0\)

于是有\(f_{k,j-k}\binom{i}{k}\to f_{i,j}\)\(g_{k,j-k}\binom{i}{k}+f_{k,j-k}\binom{i}{k}\min(k,r)\to g_{i,j}\)

直接用long double算没有爆精度,佩服。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define db long double
#define N 1005
int n,d,r;
db C[N][N];
void initC(int n){
	for (int i=0;i<=n;++i){
		C[i][0]=1;
		for (int j=1;j<=i;++j)
			C[i][j]=C[i-1][j-1]+C[i-1][j];
	}
}
db f[N][N],g[N][N];
int main(){
	scanf("%d%d%d",&n,&d,&r);
	initC(n+d);
	f[0][0]=1,g[0][0]=0;
	for (int i=1;i<=n;++i){
		for (int j=0;j<=d;++j)
			for (int k=0;k<=j && k<=i;++k){
				f[i][j]+=f[k][j-k]*C[i][k];
				g[i][j]+=g[k][j-k]*C[i][k]+min(k,r)*f[k][j-k]*C[i][k];
			}
	}
	printf("%.7lf\n",(double)(g[n][d]/C[n+d-1][d])+r);
	return 0;
}
posted @ 2020-10-20 19:35  jz_597  阅读(267)  评论(0编辑  收藏  举报