2018 ACM-ICPC World Finals Problem D. Gem Island(递推)

2018 ACM-ICPC World Finals Problem D. Gem Island

题目大意

  • n n n个人,初始每个人手上有一颗宝石,每天等概率有一颗宝石变为两颗,求 d d d天后宝石数最多的 r r r个人的期望宝石总数。
  • 1 ≤ n , d ≤ 500 1le n,dle500 1n,d500 1 ≤ r ≤ n 1le rle n 1rn

题解

  • 首先通过手玩样例可以发现,最终的每种状态出现的次数都是相等的,均为 d ! d! d!次,不同的操作方案有 n ∗ ( n + 1 ) ∗ . . . ∗ ( n + d − 1 ) n*(n+1)*...*(n+d-1) n(n+1)...(n+d1) ( n + d − 1 ) ! ( n − 1 ) ! frac{(n+d-1)!}{(n-1)!} (n1)!(n+d1)!种,那么考虑先求出各种不同的最终状态,再乘上和除去相应的数。
  • 则相当于求和为 n + d n+d n+d n n n个数,且保证每个数至少为 1 1 1的所有方案的前 r r r个数之和。
  • 不难想到可以设状态递推,按照很普通的设法,设 f i , j f_{i,j} fi,j表示到第 i i i个人,此时前 i i i个数和为 j j j的答案,每次枚举第 i i i个人的数来转移,但是发现这样并不能统计“前 r r r大”这一条件。如果多设一维的话,复杂度无法满足要求。
  • 不妨换一个角度,考虑起初每个人都是 1 1 1个共 n n n个,然后选 s 1 s1 s1人变成 2 2 2个,总数变成 n + s 1 n+s1 n+s1个,方案数乘上 ( n s 1 ) nchoose s1 (s1n);接着在 s 1 s1 s1人中选 s 2 s2 s2人变成 3 3 3个,总数变成 n + s 1 + s 2 n+s1+s2 n+s1+s2个,方案数乘上 ( s 1 s 2 ) s1choose s2 (s2s1),以此类推。
  • 这样子可以少去记录第几个人的状态,而可以用来记录上一次加入了多少人,每次对最终前 r r r大的贡献为 m i n ( r , s x ) min(r,s_x) min(r,sx) s x s_x sx为当前加入的个数。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ld long double
#define N 510
ld f[N * 2][N], g[N * 2][N], c[N][N];
int main() {
	int n, d, r, i, j, k;
	scanf("%d%d%d", &n, &d, &r);
	for(i = 0; i <= n; i++) {
		c[i][0] = c[i][i] = 1;
		for(j = 1; j < i; j++) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
	}
	f[n][n] = r, g[n][n] = 1;
	for(i = n; i < n + d; i++) {
		for(j = 1; j <= n; j++) if(g[i][j]) {
			for(k = 1; k <= j && i + k <= n + d; k++) {
				g[i + k][k] += g[i][j] * c[j][k];
				f[i + k][k] += f[i][j] * c[j][k] + g[i][j] * c[j][k] * min(k, r);
			}
		}
	}
	ld s = 0;
	for(i = 1; i <= n; i++) s += f[n + d][i];
	for(i = n; i <= n + d - 1; i++) s /= i; 
	for(i = d; i; i--) s *= i;
	printf("%.10LF", s);
	return 0;
}

自我小结

  • 注意方案数和贡献同时转移的递推中,贡献转移需要乘上转移的方案数。
  • 程序修改时要注意原数组的大小是否适合新做法,要检查极限数据情况下的数组是否越界。
哈哈哈哈哈哈哈哈哈哈
原文地址:https://www.cnblogs.com/LZA119/p/14608426.html