点此看题
为了计数方便,我们把每个人看成不同的,我声称这对最后答案没有任何影响。
因为宝石的变化是放在宝石上的,所以我们以宝石为主体进行数学分析。每个宝石不同,我们根据宝石分裂情况来区分方案,那么总方案是 \(\frac{(n+d-1)!}{(n-1)!}\),然后我们考虑末状态每个人多得到了 \(a_i\) 个宝石,对应的方案是 \(\frac{d!}{\prod a_i!}\times \prod a_i!=d!\),前一项意思是我们选出 \(a_i\) 个宝石给第 \(i\) 个人,把组合数拆开得到的式子,后一项的意思是每个人得到宝石后,让哪个宝石分裂得到的方案。综上可以得到:每种末状态是等概率出现的。
根据上面的性质,我们可以发现只需要求出末状态的总方案数和总和就行了,中间过程不用管了。可以设计 \(dp\) 解决这个问题,设 \(cnt[i][j]\) 表示考虑 \(i\) 个人,用了 \(j\) 个宝石的方案数,\(sum[i][j]\) 类似表示前 \(r\) 个人宝石个数总和,转移:
\[cnt[i][j]=\sum_k {i\choose k}\cdot cnt[k][j-k] \]\[sum[i][j]=\sum_k{i\choose k}\cdot (sum[k][j-k]+cnt[k][j-k]\cdot\min(k,r)) \]最后的答案是 \(\frac{sum[n][d]}{cnt[n][d]}+r\)
这个 \(dp\) 的原理值得深究,它是这样运行的:
柱形图代表 \(a_i\),转移代表着红线,红线切到的柱子我们就把它的 \(a_i\) 加 \(1\),没有情况会漏掉,而且以前加了的柱子以后还要加,这种 \(dp\) 的特性是先考虑的柱子的高度是最高的,它天然带有大小关系。
考虑方案时,先要明确什么量考虑为相同,什么量考虑为不同,才能有的放矢。
多过程的题(要分析清楚每个过程),如果具体过程难以维护,我们不妨考虑末状态具有什么性质,能不能直接算?
#include <cstdio> #include <iostream> using namespace std; #define db long double const int M = 505; int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int n,d,r;db cnt[M][M],sum[M][M],C[M][M]; signed main() { n=read();d=read();r=read(); C[0][0]=1; for(int i=1;i<=max(n,d);i++) { C[i][0]=1; for(int j=1;j<=i;j++) C[i][j]=C[i-1][j-1]+C[i-1][j]; } cnt[0][0]=1; for(int i=1;i<=n;i++) for(int j=0;j<=d;j++) for(int k=0;k<=min(i,j);k++) { cnt[i][j]+=C[i][k]*cnt[k][j-k]; sum[i][j]+=C[i][k]*(sum[k][j-k] +min(r,k)*cnt[k][j-k]); } double ans=sum[n][d]/cnt[n][d]+r; printf("%.7lf\n",ans); }