[ICPC World Finals 2018] 绿宝石之岛
一、题目
二、解法
为了计数方便,我们把每个人看成不同的,我声称这对最后答案没有任何影响。
因为宝石的变化是放在宝石上的,所以我们以宝石为主体进行数学分析。每个宝石不同,我们根据宝石分裂情况来区分方案,那么总方案是 \(\frac{(n+d-1)!}{(n-1)!}\),然后我们考虑末状态每个人多得到了 \(a_i\) 个宝石,对应的方案是 \(\frac{d!}{\prod a_i!}\times \prod a_i!=d!\),前一项意思是我们选出 \(a_i\) 个宝石给第 \(i\) 个人,把组合数拆开得到的式子,后一项的意思是每个人得到宝石后,让哪个宝石分裂得到的方案。乘上选出 \(\{a_i\}\) 的方案可以得到:\({n+d-1\choose n-1}\cdot d!=\frac{(n+d-1)!}{(n-1)!}\),综上可以得到:每种末状态是等概率出现的。
根据上面的性质,我们可以发现只需要求出末状态的总方案数和总和就行了,中间过程不用管了。可以设计 \(dp\) 解决这个问题,设 \(cnt[i][j]\) 表示考虑 \(i\) 个人,用了 \(j\) 个宝石的方案数,\(sum[i][j]\) 类似表示前 \(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);
}