[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\) 个人宝石个数总和,转移:

\[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);
}
posted @ 2021-07-13 22:48  C202044zxy  阅读(903)  评论(0编辑  收藏  举报