​P3239 [HNOI2015]亚瑟王​

乍一看很难下手,第 i i i张牌是否发动和前面的牌是否发动相关

但是我们发现每张牌最多只会发动一次

假设我们算出了 g [ i ] g[i] g[i]表示第 i i i张牌在所有的 r r r轮游戏中发动过的概率是

那么期望就是 ∑ i = 1 n g [ i ] ∗ d [ i ] \sum\limits_{i=1}^{n}g[i]*d[i] i=1∑ng[i]∗d[i]

那 么 第 一 个 物 品 发 动 的 概 率 没 有 限 制 \color{Red}那么第一个物品发动的概率没有限制 那么第一个物品发动的概率没有限制

g [ 1 ] = 1 − ( 1 − p [ 1 ] ) r g[1]=1-(1-p[1])^r g[1]=1−(1−p[1])r

那么第二个物品呢?

容易发现,第二个物品的发动和第一个物品是否发动相关

假 设 第 一 张 牌 发 动 过 技 能 , 说 明 有 一 轮 游 戏 提 前 结 束 了 假设第一张牌发动过技能,说明有一轮游戏提前结束了 假设第一张牌发动过技能,说明有一轮游戏提前结束了

那么第二张牌发动概率是 1 − ( 1 − p [ 2 ] ) r − 1 1-(1-p[2])^{r-1} 1−(1−p[2])r−1

假 设 第 一 张 牌 没 有 发 动 过 技 能 , 那 么 所 有 r 轮 游 戏 都 可 以 让 第 二 张 牌 选 择 假设第一张牌没有发动过技能,那么所有r轮游戏都可以让第二张牌选择 假设第一张牌没有发动过技能,那么所有r轮游戏都可以让第二张牌选择

那么第二张牌发动概率是 1 − ( 1 − p [ 2 ] ) i 1-(1-p[2])^i 1−(1−p[2])i

所以我们发现,我们需要知道在前 i i i张牌中有 j j j张发动的概率是多少

这样就可以很方便的解得 g [ i ] g[i] g[i]

令 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i张牌在所有的 r r r轮游戏中,恰好发动过 j j j张牌的概率

那么当第 i i i张牌发动的时候,从 f [ i − 1 ] [ j − 1 ] f[i-1][j-1] f[i−1][j−1]转移而来

此时有 j − 1 j-1 j−1轮游戏提前结束,有 r − j + 1 r-j+1 r−j+1轮游戏可以让第 i i i张牌发动

那么 f [ i ] [ j ] + = f [ i − 1 ] [ j − 1 ] ∗ ( 1 − ( 1 − p [ i ] ) r − j + 1 ) f[i][j]+=f[i-1][j-1]*(1-(1-p[i])^{r-j+1}) f[i][j]+=f[i−1][j−1]∗(1−(1−p[i])r−j+1)

那么当第 i i i张牌未发动,从 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]转移而来

那么 f [ i ] [ j ] + = f [ i − 1 ] [ j ] ∗ ( 1 − p [ i ] ) r − j f[i][j]+=f[i-1][j]*(1-p[i])^{r-j} f[i][j]+=f[i−1][j]∗(1−p[i])r−j

这样问题就迎刃而解了

#include <bits/stdc++.h>
using namespace std;
const int maxn=309;
int n,r,d[maxn],t;
double p[maxn],f[maxn][maxn],g[maxn],pw[maxn][maxn];
int main()
{
cin >> t;
while( t-- )
{
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
cin >> n >> r;
for(int i=1;i<=n;i++)
scanf("%lf%d",&p[i],&d[i] ),pw[i][0]=1;
if( !r )
{
cout << "0.0000000000\n";
continue;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=r;j++)
pw[i][j] = pw[i][j-1]*( 1.0-p[i] );
f[1][0]=pw[1][r];//r轮都不发动
f[1][1]=1-pw[1][r];
for( int i=2;i<=n;i++ )
for( int j=0;j<=min(i,r);j++ )//前i张牌有
{
//下面是什么意思呢?
//代表前i张牌有j张发动,且第i张牌发动过
//那么第i张牌有j-1轮无法发动
if( j ) f[i][j]+=f[i-1][j-1]*1.0*( 1.0-pw[i][r-j+1] );
//代表第i张牌没有发动过
//其中有j轮比赛一定无法发动
if( i!=j ) f[i][j]+=f[i-1][j]*pw[i][r-j];
}
g[1] = 1.0-pw[1][r];
for(int i=2;i<=n;i++)
for(int j=0;j<=min(i-1,r);j++ )
g[i]+=f[i-1][j]*( 1.0-pw[i][r-j] );
//第i张发动过的概率需要前i-1张没有发动
double ans=0;
for(int i=1;i<=n;i++) ans+=d[i]*g[i];
printf("%.10lf\n",ans);
}
}