[Tkey] CodeForces 1267G Game Relics

太神了这题,膜拜出题人 orz。

思考一

首先是大家都提到的一点,先抽卡再买。这里来做个数学分析。

假设我们还剩 \(k\) 种没有买,其实我们是有式子来算出它的花费期望的。WIKI 上提到,假设一个事件的概率为 \(p\),则遇到它的期望为 \(\frac{1}{p}\),因此,对于这个题,抽到一个新物品的概率显然为 \(\frac{k}{n}\),那么期望次数就为 \(\frac{n}{k}\),这里面除了最后一次之外,其余的次数的花费都为 \(x-\frac{x}{2}=\frac{x}{2}\),而最后一次的花费恰好为 \(x\),因此可以得出\(k\) 种没买的情况下,抽到新物品的期望花费公式:

\[E(k)=\frac{x}{2}\times (\frac{n}{k}-1)+x \]

可以看出这个式子是关于 \(k\) 单调递增的,也就是说,越晚抽卡,花费的代价越大,因此才有了上述的结论:先抽卡再买。

思考二

既然我们能直接算出期望了,那我们直接比较一下抽卡和买哪个更赚不就行了吗。现在抽卡的期望我们有了,还需要一个买东西的期望。其实,因为每个东西的价值都是固定的,所以这个期望实际上就是平均数,那么我们算出这个平均数,每次都和当前的抽卡期望比较,选个最小的执行不就行了,搞定!紫题也不过如此。

但是不对,有点小问题。因为每个物品的价值都是不一样的,我们抽到不同的物品,最后计算出来的平均数也不一样。难道要枚举一遍全部的可能情况,再算一个平均数的平均数?又或者还要分情况讨论?那不就变成搜了吗,晕。

思考三

不对,除了搜还能写 DP,但是就是不知道状态怎么设计。

想想看,我们现在需要的东西有什么。需要计算抽卡期望,那么我们需要的东西就是当前剩余的物品个数,还有一个是平均数,那我们直接用剩余价值除以剩余个数不就行了吗!开一个两个维度的 DP:剩余个数,剩余价值。发现它变成了一个背包 DP

现在来想状态转移。其实到这一步就和常规背包 DP 差不多了。定义初始状态 \(f_{n,sum}=sum\),即不抽卡的期望花费为总价值,然后从剩余容量由大到小开始枚举所有物品,然后尝试转移就行了。

思考四

本来以为都快过了,但是写一半卡住了。

具体是卡在状态转移的时候,突然发现这个转移根本就没法实现。因为我们每次转移可能的来源有两个:一个是购买,一个是抽卡。买还好说,但是抽卡不行,因为不是整数,而我们没办法访问一个小数下标。

那么怎么办呢,感觉又到了瓶颈了。

首先这个思路是绝对正确的,我们整理一下现在都有什么了。我们可以求出在某个状态下的最优代价,那么,假如我们再维护一个出现概率的话,就能根据期望的定义,直接用概率乘代价去算了。那么我们不妨把我们设计的 DP 数组改一下,不维护期望了,改成维护当前状态的概率,再求一下抽卡期望和平均值的最小值,二者一乘就是答案。又因为这个题的总方案数是一定的(可以用组合数求),那么我们不妨再转换一下思路,改成求当前状态的总方案数,和总方案数一比就是答案。

思考五

我们在思路整理的时候忽略了一些小细节,比如,如果当前的最优策略是抽卡,则若没抽中,下次的最优策略还是抽卡,直到抽到一个新的。简单证明一下,假如你抽卡没抽中,那么除了钱少了点,此外什么都没变,因此 \(E(k)\) 和平均数完全不会有任何变化。知道了这点,我们才能用 \(E(k)\) 这样一条路走到黑的模型来计算答案。

还有一点,打完提交的时候莫名其妙 RE 了,后来发现是阶乘乘炸了(\(100!\) 确实还挺大的),然后把阶乘函数改成 Double 就过了,会丢精度,但是不影响答案。

代码

#include<bits/stdc++.h>
using namespace std;
#define div *1.0/
int n,x;
int c[101];
double f[101][10001];
long double fac[101];
inline void prework(){
	fac[0]=1;
	for(int i=1;i<=100;++i){
		fac[i]=fac[i-1]*i;
	}
}
inline double E(int k){
	return (n div k -1)*(x div 2)+x;
}
inline double C(int x,int y){
	return fac[x] div (fac[y]*fac[x-y]);
}
int main(){
	int sum=0;
	cin>>n>>x;
	prework();
	for(int i=1;i<=n;++i){
		cin>>c[i];
		sum+=c[i];
	}
	f[n][sum]=1;
	for(int i=1;i<=n;++i){
		for(int j=0;j<=n-1;++j){
			for(int k=0;k<=sum-c[i];++k){
				f[j][k]+=f[j+1][k+c[i]];
			}
		}
	}
	long double ans=0;
	for(int i=1;i<=n;++i){
		for(int j=0;j<=sum;++j){
			ans+=(f[i][j] div C(n,n-i)) * min(j div i,E(i));
		}
	}
	printf("%.20Lf",ans);
}
posted @ 2024-06-08 09:38  HaneDaniko  阅读(19)  评论(0编辑  收藏  举报
/**/