「HNOI 2015」亚瑟王

(Description)

(n)张卡牌,每一张卡牌有(p_i)的概率发动,并造成(d_i)点伤害.一共有(r)轮,每一轮按照编号从小到大依次考虑,如果这张牌已经发动过则跳过该牌,否则以(p_i)的概率发动,如果发动成功则造成伤害然后结束该轮,否则跳过这张牌.问期望造成的伤害,(T)组询问

(n<=220,r<=132,T<=444)

(Solution)

这道的答案怎么算应该挺好想的吧.

[sum_{i=1}^n dp[i]*d[i] ]

(dp[i]) 表示第i张牌出现的概率.

但是现在问题就是怎么算(dp)数组啊?

(dp[1])显然啊.为:

[1-(1-p[1])^r ]

((1-p[1])^r)为一直不出的概率.

但是现在好像貌似只能看出来(dp[1])(QAQ)

接续上(dp)了,我们令(f[i][j])为在(r)轮中,前(i)张卡中一共出了(j)张的概率,

至于转移方程,还需要分类讨论一下:

(Case 1:)

(f[i][j])(f[i-1][j-1])转移

这表示选了第(i)张牌,现在在(r)轮中有(j-1)轮选的是(i)之前的牌,而(i)没有被选到,所以(i)被选到的轮数为(:r-j+1)

转移方程为:

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

$ Case 2:( )f[i][j](由)f[i-1][j]$转移而来.

表示不选第i张牌

那么在(r)轮中已经过了(r-j)轮了,剩下的不选(i)的概率为((1-p[i])^{r-j})

所以转移方程为:

[f[i][j]=f[i-1][j]*(1-p[i])^{r-j} ]

现在算出来了(f)数组,接下来就要来算(dp)数组了

[dp[i]=f[i-1][j]*(1-(1-p[i])^{r-j} ]

(Code)

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<cstring>
#define rg register
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
    return f*x;
}
int d[1001],r,n;
double f[1001][1001],power[1001][1001],p[1001],dp[1001];
void init(){
	memset(f,0,sizeof(f));
	memset(dp,0,sizeof(dp));
	n=read(),r=read();
	for(int i=1;i<=n;i++)
		scanf("%lf",&p[i]),d[i]=read();
	for(int i=1;i<=n;i++){
		power[i][0]=1;
		for(int j=1;j<=r;j++)
			power[i][j]=power[i][j-1]*(1-p[i]);
	}
}
void solve(){
    init();
	f[1][0]=power[1][r],f[1][1]=dp[1]=1.0-f[1][0];
	for(int i=2;i<=n;i++)
		for(int j=0;j<=min(i,r);j++){
			if(j)
				f[i][j]+=f[i-1][j-1]*(1.0-power[i][r-j+1]);
			if(i!=j) f[i][j]+=f[i-1][j]*power[i][r-j];
		}
	for(int i=2;i<=n;i++)
        for(int j=0;j<=min(i-1,r);j++)
			dp[i]+=f[i-1][j]*(1-power[i][r-j]);
	double ans=0;
	for(int i=1;i<=n;i++)
		ans+=dp[i]*d[i];
	printf("%0.10lf
",ans);
}
int main(){
	int T=read();
	while(T--)
		solve();
}
原文地址:https://www.cnblogs.com/hbxblog/p/10413908.html