题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4008
题目分析:一道很厉害的DP,做法和背包类似。记g[i][j]表示前i张卡牌,有j张卡牌发动了技能的概率,那么存在如下转移:
g[i+1][j]=g[i][j]∗(1−p[i+1])r−j
g
[
i
+
1
]
[
j
]
=
g
[
i
]
[
j
]
∗
(
1
−
p
[
i
+
1
]
)
r
−
j
g[i+1][j+1]=g[i][j]∗(1−(1−p[i+1])r−j)
g
[
i
+
1
]
[
j
+
1
]
=
g
[
i
]
[
j
]
∗
(
1
−
(
1
−
p
[
i
+
1
]
)
r
−
j
)
转移时再用一个f数组记录期望即可。
为什么第二条式子乘以的是r-j次机会中至少选一次的概率,而不是刚好选一次的概率呢?我一开始也有些疑惑,后来看了这篇blog才明白。这是因为选了第一次之后,后面的回合会以1的概率跳过它。
代码很短,大概是我近期写过的最短的代码了。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=233;
typedef long double LD;
LD f[maxn][maxn];
LD g[maxn][maxn];
LD pk[maxn][maxn];
LD p[maxn];
int d[maxn];
int t,n,r;
int main()
{
freopen("4008.in","r",stdin);
freopen("4008.out","w",stdout);
scanf("%d",&t);
while (t--)
{
memset(f,0.0,sizeof(f));
memset(g,0.0,sizeof(g));
g[0][0]=1.0;
scanf("%d%d",&n,&r);
for (int i=1; i<=n; i++)
scanf("%Lf%d",&p[i],&d[i]),p[i]=1.0-p[i],pk[i][0]=1.0;
for (int i=1; i<=n; i++)
for (int j=1; j<=r; j++)
pk[i][j]=pk[i][j-1]*p[i];
for (int i=0; i<n; i++)
for (int j=0; j<=r; j++)
{
g[i+1][j]+=(g[i][j]*pk[i+1][r-j]);
f[i+1][j]+=(f[i][j]*pk[i+1][r-j]);
if (j==r) continue;
g[i+1][j+1]+=(g[i][j]*(1.0-pk[i+1][r-j]));
f[i+1][j+1]+=((f[i][j]+(LD)d[i+1]*g[i][j])*(1.0-pk[i+1][r-j]));
}
LD ans=0.0;
for (int i=0; i<=r; i++) ans+=f[n][i];
printf("%.10Lf\n",ans);
}
return 0;
}