HNOI的题真不是人做的。。
要求期望的话,根据期望=权值*概率,我们只要计算出一张牌在r轮中出现的总概率乘以d[i]即可
那么现在就要求出这个概率。
由于一张牌在这一轮是否被抽中只与之前的有关,那么我们就可以从上一次的状态中转移过来。
首先我们设第i张牌在r轮中出现的总概率为a[i]
我们发现直接计算出现的概率有些困难,所以我们可以计算他不出现的概率然后在用1减去他
换句话说:
我们知道一张牌在议一轮出现的概率为p[i]的话那么一轮不出现的概率就是:
(1−p[i])
(
1
−
p
[
i
]
)
根据概率论,那么我们在r轮中都不出现的概率就是:
(1−p[i])r
(
1
−
p
[
i
]
)
r
那么第一张牌也就是a[0]是很容易求得的
a[0]=1−(1−p[0])r
a
[
0
]
=
1
−
(
1
−
p
[
0
]
)
r
那么我们或许会想当然的认为后面的概率也是这么算,那就错了!!
因为题目中有这样一句话
如果技能发动,则对敌方造成 di点伤害,并结束这一轮。
那么我们就需要再次设立未知数
设f[i][j]表示前i张牌已经出了j张的概率,那么
a[i]=∑rj=0f[i−1][j]∗(1−(1−p[i])r−j)(i>0)
a
[
i
]
=
∑
j
=
0
r
f
[
i
−
1
]
[
j
]
∗
(
1
−
(
1
−
p
[
i
]
)
r
−
j
)
(
i
>
0
)
那我们就可以在dp中把a[i]顺便统计
那么我们再来推一下状态转移方程,可以从两个地方转移过来:
f[i][j]+=(f[i−1][j]∗(1−p[i])r−j)+(f[i−1][j−1]∗(1−(1−p[i])r−j+1)
f
[
i
]
[
j
]
+
=
(
f
[
i
−
1
]
[
j
]
∗
(
1
−
p
[
i
]
)
r
−
j
)
+
(
f
[
i
−
1
]
[
j
−
1
]
∗
(
1
−
(
1
−
p
[
i
]
)
r
−
j
+
1
)
前一个括号的情况是这一轮不选,那么概率是
(1−p[i])r−j
(
1
−
p
[
i
]
)
r
−
j
后一个括号就是这一轮要选,那么概率是
(1−(1−p[i])r−j+1)
(
1
−
(
1
−
p
[
i
]
)
r
−
j
+
1
)
我的代码里面学习大佬用了预处理幂,因为转移的过程中会有多次计算到一个幂的情况,可以用这个来加速
code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
double f[250][150],a[250];//f[i][j]:第i张牌,第j轮出的概率,a[i]:第i张牌在r轮中出的总概率(最后乘上d[i]就是答案)
double powp[250][150];
double p[250];
int d[250];
/*
a[1]=1-(1-p[1])^r
a[i]=sigma(j-i)f[i-1][j]*(1-(1-p[i])^r-j) (i>0)
f[i][j]+=f[i-1][j]*(1-p[i])^r-j(i>0)
f[i][j]+=f[i-1][j-1]*(1-(1-p[i])^r-j+1) (i>0,j>0)
*/
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n,r;scanf("%d%d",&n,&r);
for(int i=0;i<n;i++)
{
scanf("%lf%d",&p[i],&d[i]);
}
for(int i=0;i<n;i++)//计算(1-p[i])^j
{
powp[i][0]=1;
for(int j=1;j<=r;j++)
{
powp[i][j]=powp[i][j-1]*(1-p[i]);
}
}
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
f[0][0]=powp[0][r];//第0轮当然不出
f[0][1]=a[0]=1-(powp[0][r]);
for(int i=1;i<n;i++)
{
for(int j=0;j<=r;j++)
{
a[i]+=f[i-1][j]*(1-powp[i][r-j]);
f[i][j]+=f[i-1][j]*powp[i][r-j];
if(j>0) f[i][j]+=f[i-1][j-1]*(1-powp[i][r-j+1]);
}
}
double ans=0;
for(int i=0;i<n;i++)
{
ans+=a[i]*d[i];
}
printf("%.10f\n",ans);
}
return 0;
}