题意:
给你n个数,你每次会取一张牌,你可以随时结束游戏,问你最终你手上牌的值的和落在a+1到b这个区间内的概率是多少
题解:
我看了题解和一些人的代码,说实话我一开始是很蒙逼的
为什么这里要乘这个,为什么这里要除这个,搞了一段时间才懂。
思路是枚举每一张牌作为最后一张符合要求的牌的时候的概率。
我们要处理出
a
+
1
−
x
[
i
]
a+1-x[i]
a+1−x[i]到
b
−
x
[
i
]
b-x[i]
b−x[i]区间的所有可行解,那么就用背包来求。
我们设dp[f][i][j]表示在当前状态为f(0|1,这是滚动数组)的时候,取j张牌使得和为k的时候的概率。
那么背包就是
dp[i&1][j][k]+=1.0*dp[(i&1)^1][j-1][k-v[i]]*j/(n-j+1);
为什么要*j,因为我们已知当前有j张牌,取出一张变成j-1张牌的情况数有j种。
对于这张牌,有
1
n
−
j
+
1
\frac{1}{n-j+1}
n−j+11的概率被取出。
首先如果每一次都做一遍背包的话,会T,这时候需要用到逆背包的知识,什么是逆背包,就是将这个数从前往后做,将这个数的影响去掉的操作,至于加回来的时候是从后往前加回来。
为什么从前往后去掉影响。
假设我们现在有一个值得影响为1,背包是
1 2 3 4 5
那么我们要去掉影响的时候,应当从前往后减
1 1 2 2 3
那么我们从后往前背包的话,答案就变回
1 2 3 4 5
了。
dp的时候/n-j的意义与上面相同,也就是从n-j个牌中选出一张的概率。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=505;
int v[N];
double dp[2][N][N];
int main()
{
int n,a,b;
scanf("%d%d%d",&n,&a,&b);
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
for(int j=0;j<=i;j++)
for(int k=0;k<=b;k++)
dp[i&1][j][k]=dp[(i&1)^1][j][k];
for(int j=1;j<=i;j++)
for(int k=v[i];k<=b;k++)
dp[i&1][j][k]+=1.0*dp[(i&1)^1][j-1][k-v[i]]*j/(n-j+1);
}
double ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
for(int k=v[i];k<=b;k++)
dp[n&1][j][k]-=1.0*dp[n&1][j-1][k-v[i]]*j/(n-j+1);
for(int j=0;j<n;j++)
for(int k=max(0,a+1-v[i]);k<=min(a,b-v[i]);k++)
ans+=1.0*dp[n&1][j][k]/(n-j);
for(int j=n;j>=1;j--)
for(int k=v[i];k<=b;k++)
dp[n&1][j][k]+=1.0*dp[n&1][j-1][k-v[i]]*j/(n-j+1);
}
printf("%.15f\n",ans);
return 0;
}

本文深入解析了一道关于概率背包的问题,通过枚举每张牌作为最后一张符合条件的牌时的概率,利用背包算法求解所有可行解,详细阐述了正向与逆向背包操作的原理及其在解决实际问题中的应用。

被折叠的 条评论
为什么被折叠?



