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
1≤n,d≤500,
1
≤
r
≤
n
1le rle n
1≤r≤n
题解
首先通过手玩样例可以发现,最终的每种状态出现的次数都是相等的,均为
d
!
d!
d!次,不同的操作方案有
n
∗
(
n
+
1
)
∗
.
.
.
∗
(
n
+
d
−
1
)
n*(n+1)*...*(n+d-1)
n∗(n+1)∗...∗(n+d−1)即
(
n
+
d
−
1
)
!
(
n
−
1
)
!
frac{(n+d-1)!}{(n-1)!}
(n−1)!(n+d−1)!种,那么考虑先求出各种不同的最终状态,再乘上和除去相应的数。
则相当于求和为
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>usingnamespace std;#define ld long double#define N 510
ld f[N *2][N], g[N *2][N], c[N][N];intmain(){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);return0;}