题意:
给你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;
}