[Tkey] CodeForces 1267G Game Relics
太神了这题,膜拜出题人 orz。
思考一
首先是大家都提到的一点,先抽卡再买。这里来做个数学分析。
假设我们还剩
可以看出这个式子是关于
思考二
既然我们能直接算出期望了,那我们直接比较一下抽卡和买哪个更赚不就行了吗。现在抽卡的期望我们有了,还需要一个买东西的期望。其实,因为每个东西的价值都是固定的,所以这个期望实际上就是平均数,那么我们算出这个平均数,每次都和当前的抽卡期望比较,选个最小的执行不就行了,搞定!紫题也不过如此。
但是不对,有点小问题。因为每个物品的价值都是不一样的,我们抽到不同的物品,最后计算出来的平均数也不一样。难道要枚举一遍全部的可能情况,再算一个平均数的平均数?又或者还要分情况讨论?那不就变成搜了吗,晕。
思考三
不对,除了搜还能写 DP,但是就是不知道状态怎么设计。
想想看,我们现在需要的东西有什么。需要计算抽卡期望,那么我们需要的东西就是当前剩余的物品个数,还有一个是平均数,那我们直接用剩余价值除以剩余个数不就行了吗!开一个两个维度的 DP:剩余个数,剩余价值。发现它变成了一个背包 DP。
现在来想状态转移。其实到这一步就和常规背包 DP 差不多了。定义初始状态
思考四
本来以为都快过了,但是写一半卡住了。
具体是卡在状态转移的时候,突然发现这个转移根本就没法实现。因为我们每次转移可能的来源有两个:一个是购买,一个是抽卡。买还好说,但是抽卡不行,因为不是整数,而我们没办法访问一个小数下标。
那么怎么办呢,感觉又到了瓶颈了。
首先这个思路是绝对正确的,我们整理一下现在都有什么了。我们可以求出在某个状态下的最优代价,那么,假如我们再维护一个出现概率的话,就能根据期望的定义,直接用概率乘代价去算了。那么我们不妨把我们设计的 DP 数组改一下,不维护期望了,改成维护当前状态的概率,再求一下抽卡期望和平均值的最小值,二者一乘就是答案。又因为这个题的总方案数是一定的(可以用组合数求),那么我们不妨再转换一下思路,改成求当前状态的总方案数,和总方案数一比就是答案。
思考五
我们在思路整理的时候忽略了一些小细节,比如,如果当前的最优策略是抽卡,则若没抽中,下次的最优策略还是抽卡,直到抽到一个新的。简单证明一下,假如你抽卡没抽中,那么除了钱少了点,此外什么都没变,因此
还有一点,打完提交的时候莫名其妙 RE 了,后来发现是阶乘乘炸了(
代码
#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);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!