洛谷 P3239 [HNOI2015]亚瑟王(期望+dp)

题面传送门

感觉是道挺好的题,可惜当时没写题解来着的?
根据期望的线性公式,我们求出每个卡牌被发动的概率 \(q_i\),然后

\[ans=\sum\limits_{i=1}^np_id_i \]

于是我们求出 \(q_i\) 即可。
我们设 \(dp_{i,j}\) 表示在前 \(i\) 张牌里发动了 \(j\) 张牌的概率。
如果已知 \(dp_{i,j}\),那么可以这样求出 \(q_i\):

\[q_i=\sum\limits_{j=0}^rdp_{i-1,j}+(1-(1-p_i)^{r-j}) \]

稍微解释一下这个式子。我们枚举在前 \(i-1\) 个牌里面发动了 \(j\) 个牌。这意味着有 \(j\) 轮不会考虑到第 \(i\) 张牌。在剩下 \(r-j\) 轮当中,\(i\) 号卡牌一次都没被发动的概率为 \((1-p_i)^{r-j}\),\(1-(1-p_i)^{r-j}\) 就是卡牌 \(i\) 至少被发动一次的概率。
那么怎样求 \(dp_{i,j}\) 呢,其实用背包的套路就可以了。分两种情况:

  1. 如果卡牌 \(j\) 被选,那么 \(dp_{i,j}\) 可以从 \(dp_{i-1,j-1}\) 转移过来。有 \(r-j+1\) 轮会考虑到卡牌 \(i\),卡牌 \(i\) 发动的概率为 \((1-(1-p_i)^{r-j+1})\)。
  2. 如果卡牌 \(j\) 没被选,那么 \(dp_{i,j}\) 可以从 \(dp_{i-1,j}\) 转移过来。有 \(r-j\) 轮会考虑到卡牌 \(i\),卡牌 \(i\) 未被发动的概率为 \((1-p_i)^{r-j}\)。

综上 \(dp_{i,j}=dp_{i-1,j-1}\times(1-(1-p_i)^{r-j+1})+dp_{i,j}\times(1-p_i)^{r-j}\)
于是这题就做完了,复杂度 \(\mathcal O(Tnr)\)。
另外预处理 \(1-p_i\) 的幂。快速幂会多一个 \(\log\)

#include <bits/stdc++.h>
using namespace std;
#define fi			first
#define se			second
#define pb			push_back
#define fz(i,a,b)	for(int i=a;i<=b;i++)
#define fd(i,a,b)	for(int i=a;i>=b;i--)
#define foreach(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define all(a)		a.begin(),a.end()
#define fill0(a)	memset(a,0,sizeof(a))
#define fill1(a)	memset(a,-1,sizeof(a))
#define fillbig(a)	memset(a,0x3f,sizeof(a))
#define y1			y10101010101
#define y0			y01010101010
typedef pair<int,int> pii;
typedef long long ll;
int T,n,r,d[222];
double p[222],dp[222][222];
double pw[222][222];
inline void clear(){
	for(int i=1;i<=n;i++) d[i]=0;
	for(int i=1;i<=n;i++) p[i]=0;
	for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) dp[i][j]=0;
	for(int i=0;i<=n;i++) for(int j=0;j<=r;j++) pw[i][j]=0;
}
inline void solve(){
	scanf("%d%d",&n,&r);clear();//remember to clear the data!
	for(int i=1;i<=n;i++) scanf("%lf%d",&p[i],&d[i]);
	for(int i=1;i<=n;i++){
		pw[i][0]=1;
		for(int j=1;j<=r;j++){
			pw[i][j]=pw[i][j-1]*(1.0-p[i]);//calculate the power of 1-p[i]
//			printf("%d %d %.10lf\n",i,j,pw[i][j]);
		}
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++) for(int j=0;j<=i;j++){
		dp[i][j]=dp[i-1][j]*pw[i][r-j];
		if(j) dp[i][j]+=dp[i-1][j-1]*(1-pw[i][r-j+1]);//calculate dp[i][j] using the fomula above
//		printf("%d %d %.10lf\n",i,j,dp[i][j]);
	}
	double ans=0;
	for(int i=1;i<=n;i++){
		double prob=0;
		for(int j=0;j<=i-1;j++){
			prob+=dp[i-1][j]*(1-pw[i][r-j]);//calculate p[i]
		}
//		printf("%d %.10lf\n",i,prob); 
		ans+=prob*d[i];
	}
	printf("%.10lf\n",ans);
}
int main(){
	scanf("%d",&T);
	while(T--) solve();
	return 0;
}
/*
2
3 2
0.5000 2
0.3000 3
0.9000 1
3 2
0.5000 2
0.3000 3
0.9000 1
*/
上一篇:前端各种交互效果——Tab栏效果


下一篇:力扣 leetcode 每日一题 222. 完全二叉树的节点个数