可以进行若干次抽奖,每一次分别获得 个物品的概率 都是确定的,给定一个 ,求抽到 个物品的期望抽奖次数。
如果定义 为获得 个物品的期望次数,那么这个转移方程也是十分显然:
从数学意义上思考,这个式子本身是没有问题的。
但是从程序的方面来思考,我们写一个 naive 的转移肯定是行不通的,因为这涉及到当 的时候 , 会从自身转移。
可以通过解方程的方法来从数学的角度变形这个式子:
再化简一下可以得到:
这个时候就可以发现转移方程里面不包含 本身了,可以直接转移。
这个题并没有直接给出 ,而是给出了 个相互独立的概率 ,所以开一个卡包获得 张卡的概率需要自己算。
这个题里面我们还需要人为处理一下 ,这个也考虑从 DP 的角度来解决,定义 数组为考虑了前 张卡获得了 张卡的概率,这样的话转移还是就挺明显的 :
然后剩下的就直接套上面的流程即可。
| #include<bits/stdc++.h> |
| using namespace std; |
| const int N=5010; |
| double f[N],g[N][N]; |
| int n,x; |
| double p[N]; |
| int main() |
| { |
| cin>>n>>x; |
| for(int i=1;i<=n;++i)scanf("%lf",&p[i]),p[i]/=100.0; |
| g[0][0]=1; |
| for(int i=1;i<=n;++i) |
| { |
| g[i][0]=g[i-1][0]*(1-p[i]); |
| for(int j=1;j<=i;++j) |
| g[i][j]=g[i-1][j-1]*p[i]+g[i-1][j]*(1-p[i]); |
| } |
| f[0]=0; |
| for(int i=1;i<=x;++i) |
| { |
| f[i]=1; |
| for(int j=max(0,i-n);j<i;++j) |
| { |
| f[i]+=f[j]*g[n][i-j]; |
| } |
| f[i]/=(1-g[n][0]); |
| } |
| printf("%.16lf",f[x]); |
| return 0; |
| } |
类似于完全背包,有 个不同的卡包,每一个都可以选择无限次
| #include<bits/stdc++.h> |
| using namespace std; |
| int n,m; |
| const int N=200; |
| int c[N],num[N]; |
| int s[N][N]; |
| double p[N][10010]; |
| double f[N]; |
| int main() |
| { |
| cin>>n>>m; |
| for(int i=1;i<=n;++i) |
| { |
| cin>>c[i]>>num[i]; |
| double plus=1.0/(num[i]*1.0); |
| for(int j=1;j<=num[i];++j)cin>>s[i][j],p[i][s[i][j]]+=plus; |
| } |
| for(int i=1;i<=m;++i)f[i]=1e18; |
| f[0]=0; |
| for(int i=1;i<=m;++i) |
| { |
| for(int j=1;j<=n;++j) |
| { |
| double tmp=c[j]; |
| for(int k=1;k<=m;++k) |
| tmp+=f[max(i-k,0)]*p[j][k]; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| tmp/=1-p[j][0]; |
| f[i]=min(f[i],tmp); |
| } |
| } |
| printf("%.10lf",f[m]); |
| return 0; |
| } |
这个题严格意义上来讲其实并没有涉及到从自身转移的情况,但是属于是比较难的一道期望DP,而且还是比较典型。
题意就是你可以先抛若干次骰子,每次都可以等概率地获得 的点数,记最后你获得的点数为 。紧接着另外一个人也丢若干骰子,他的投掷过程和你的完全独立,直到他获得至少 分之前,他都不会停止,记最后他获得的分数是 。
给定一个分数上界 。
如果最后你的点数 大于 , 那么你就输了。
否则:
现在询问按照最优策略行事,最后获胜的概率多少?
这是一个类博弈问题,我们尝试定义 为:从总计得到 点数之后进行操作获得的最大获胜概率,那么这时候就有两种决策:
- 再投一次骰子。
- 不投了。
对于情况 1 ,可以转化为子问题求解。
也就是
对于情况 2 ,这时候我们就要对于确定的 ,来看看 的概率分布,从而确定获胜情况。
由于两个过程是相互独立的,所以我们可以预处理出 最后停在任意一点的概率,实际问题的意义上考虑, 最后所在的位置肯定只能是 的区间内,我们定义 数组为所求的数组,发现 数组的每一个 会以 的等概率转移给他之后 的 ,那么这个区间加的过程实际上可以通过差分、树状数组、线段树的方式来优化,这里选择的是差分。
由于我们正序枚举 可能的位置,所以只会对后面的产生加减,那么一边枚举一边对差分数组进行前缀和 ,可以同时得到当前位置的概率。并且我们在 的时候就会停止前进,那么只需要枚举 的 即可。
考虑对于一个给定的 ,去计算 对应的能使之获胜的概率,显然就是 ,如果 的话那么最后一项对应是 。这个显然可以通过前缀和进行优化。
最后的转移方程就是
其中 DP 的区间和可以通过双指针的思想O(1)求出。
| #include<bits/stdc++.h> |
| using namespace std; |
| int n,l,d; |
| const int N=2e5+10; |
| double dif[N<<1],g[N<<1],sg[N<<1]; |
| inline void pre() |
| { |
| cin>>n>>l>>d; |
| g[0]=1.0; |
| dif[1]=1.0/d,dif[1+d]=-dif[1]; |
| for(int i=1;i<=l-1;++i) |
| { |
| dif[i]+=dif[i-1],g[i]=dif[i]; |
| dif[i+1]+=g[i]/d,dif[i+d+1]-=g[i]/d; |
| } |
| for(int i=l;i<=N;++i)dif[i]+=dif[i-1],g[i]=dif[i],sg[i]=sg[i-1]+g[i]; |
| } |
| double dp[N<<1]; |
| double calc_Y(int x) |
| { |
| double ans=1-sg[n]; |
| if(x)ans+=sg[x-1]; |
| return ans; |
| } |
| int main() |
| { |
| pre(); |
| double sum=0; |
| for(int i=n;i>=0;--i) |
| { |
| dp[i]=max(sum/d,calc_Y(i)); |
| sum+=dp[i],sum-=dp[i+d]; |
| } |
| printf("%.15lf",dp[0]); |
| return 0; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效