期望dp入门(待续)

并不是所有的题都要期望dp,有的可以直接算出概率然后计算期望

题目1:poj 2096:
题意:一个软件有s个子系统,会产生n种bug。
某人一天发现一个bug,这个bug属于某种bug,发生在某个子系统中。
求找到所有的n种bug,且每个子系统都找到bug,这样所要的天数的期望。
需要注意的是:bug的数量是无穷大的,所以发现一个bug,出现在某个子系统的概率是1/s,
属于某种类型的概率是1/n。
(摘自 https://www.cnblogs.com/jackge/archive/2013/05/21/3091757.html)

解法:期望dp入门题,设dp[i][j]为已经得到的bug中包括i种bug,j种系统,要得到n种bug,s个子系统bug的期望天数。显然dp[n][s] = 0,从dp[i][j]花掉一天找bug,肯能达到四种状态:(1)dp[i][j],找到的bug都是已有的.(2) dp[i+1][j] ,找到的bug是新种的。(3) dp[i][j+1] 找到的bug是新子系统的 (4) dp[i+1][j+1],找到的bug是新种的同时是新子系统的。
概率分别为:
(1) i * j / n * s
(2) (n - i) * j / n * s
(3) i * (s - j) / n * s
(4) (n - i) * (s - j) / n * s
根据全期望,期望可以由子期望加权得到,可以列出转移方程。
分别是(四种状态的期望 乘上概率) 的和,再加上1天,这个1天是做出决策转移的代价。
发现转移状态中有相同的状态,移项即可。

注意输出用 %.4f

//逆推 
#include<iostream>
using namespace std;
const int maxn = 1e3 + 10;
#include<stdio.h>
#include<string.h>
int n,s;
double dp[maxn][maxn];
int main() {
	while(~scanf("%d%d",&n,&s)){
		memset(dp,0,sizeof(dp));
		dp[n][s] = 0;
		for(int i = n; i >= 0; i--) {
			for(int j = s; j >= 0; j--) {
				if(i == n && j == s) continue;
				dp[i][j] += (double)dp[i + 1][j] * (n - i) * j / (double)(n * s);
				dp[i][j] += (double)dp[i][j + 1] * i * (s - j) / (double)(n * s);
				dp[i][j] += (double)dp[i + 1][j + 1] * (n - i) * (s - j) / (double)(n * s);
				dp[i][j] += 1.0;
				dp[i][j] /= (1 - (double) (i * j) / (n * s));
			}
		}
		printf("%.4f\n",dp[0][0]);
	}
	return 0;
}

这题也可以正推(但没必要),令dp[i][j]表示凑齐n种,s个子系统还差,i种j个子系统,决策转移方式类似。

题目2:HDU4405:
题意:在一个1×n的格子上掷色子,从0点出发,掷了多少前进几步,同时有些格点直接相连,即若a,b相连,当落到a点时直接飞向b点。求走到n或超出n期望掷色子次数
(摘自 https://blog.csdn.net/super_rudy/article/details/50577375)

解法:定义dp[i]为当前在i这个位置,走到n需要掷色子的期望次数,转移方程很简单,掷一次色子有6种可能,概率均为1/6,用全期望公式(或子概率加权和)再加上1(掷了一次色子的次数代价),可以得到dp[i]的期望。对于直接相连的点,状态可以直接转移,因为可以直接跳过去。

这题很好的体现为什么要逆推,通常我们设计出的状态都比较适合逆推,因为最终状态(dp[n])的值是已知的,而其他状态的值是未知的。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int n,m;
double dp[maxn];
int t[maxn];
int main() {
	while(scanf("%d%d",&n,&m)) {
		if(!n && !m) break;
		memset(t,0,sizeof(t));
		for(int i = 1; i <= m; i++) {
			int x,y;
			scanf("%d%d",&x,&y);
			t[x] = y;
		}	
		dp[n] = 0;
		for(int i = n - 1; i >= 0; i--) {
			dp[i] = 1;
			if(t[i] != 0) dp[i] = dp[t[i]];
			else {
				for(int j = 1; j <= 6; j++) {
					if(i + j > n) continue;
					dp[i] += dp[i + j] / 6.0;	
				}
			}
		}
		printf("%.4lf\n",dp[0]);
	}
	return 0;
}

题目3https://cometoj.com/contest/37/problem/C?redirect=%2Fcontest%2F37%2Fproblem%2FC
题目大意:有一个n个点的无向完全图G,删掉一条边的概率是 x / y,定义G的独立集S:S中任意两点a,b不存在边连接a,b。问独立集的个数的期望。

分析:i个点的独立集,要删除 i * (i - 1)条边,概率为pow(x / y,i * (i - 1)),
i个点的独立集有c[n][i]个,可以计算得到大小为i个点的独立集的期望个数:c[n][i] * pow(x / y,i * (i - 1))。
枚举i,每一个得到i个点的独立集的期望个数相加就是最后的答案。注意这里要取模求逆元。
c[n][i] 数组开不下,可以去掉一维,用递推的方式求出所有的c[i]。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const ll mod = 998244353;
long long x,y,n;
long long fpow(long long a,long long b) {
	long long res = 1;
	while(b) {
		if(b & 1) res = (res % mod) * (a % mod) % mod;
		a = (a % mod) * (a % mod) % mod;
		b >>= 1;
	}	
	return res;
}
long long ex_gcd(long long a,long long b,long long &x,long long &y) {
	if(!b) {
		x = 1;
		y = 0;
		return a;
	}
	long long g = ex_gcd(b,a % b,y,x);
	y -= x * (a / b);
	return g;
}
ll c[maxn];
int main() {
	scanf("%lld%lld%lld",&n,&x,&y);
	c[0] = 1;
	for(ll i = 1; i <= n; i++) {
		c[i] = (((c[i - 1] % mod) * (n - i + 1) % mod) * fpow(i,mod - 2)) % mod;
	}
	long long tot = n * (n - 1) / 2;
	long long res = 0;
	for(long long i = 0; i <= n; i++) {
		long long a,b;
		long long tmp = i * (i - 1) / 2;
		long long tx = fpow(x,tmp);
		long long ty = fpow(y,tmp);
		long long g = ex_gcd(tx,ty,a,b);
		tx /= g;ty /= g;
		ex_gcd(ty,mod,a,b);
		a = ((a + mod) % mod) * (tx % mod) % mod;
		a = (a * c[i]) % mod;
		res = (res + a) % mod;
	}
	printf("%lld\n",res);
	return 0;
}

题目4:ZOJ 3640 : Help Me Escape
题目大意:给你一个初始值f,有n个洞口,你每次被随机分到这n个洞口的其中一个,如果你的f>c[i](洞的防御力),那么就可以跳出了,需要的天数是p*c[i]*c[i](p是题目给的一个算式),如果f<=c[i],那么他的攻击力变为f+c[i],然后又随机到一个洞口,天数加1.求最后出去天数的期望值。
(以上文字来自:https://blog.csdn.net/coraline_m/article/details/26285891)

解法:简单分析一下:对于当前攻击力的状态,对于一个随机的洞口:打得过你就跑出去了,打不过你的战斗力会上涨,你的下一个状态只可能是这两种之一,根据全期望可以列出当前攻击力能逃出去的期望转移式子,而第二种是一个相同的子问题,因此可以想到用攻击力作状态写期望dp。

初始化dp[i] = 0;
转移式子:因为遇到每个洞口的概率都是 1 / n,可以枚举洞口。
如果当前攻击力大于这个洞口防御力,则能逃出去,dp[i] += f[i]。
否则你的战斗力会增长c[i],并且你需要再过一天才能再次随机挑战,dp[i] += dp[i + c[i]] + 1。
最后,dp[i] / n,因为每种洞口遇到的概率都是1 / n。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4 + 10;
int n,f;
double dp[maxn];
int c[maxn];
int cal(int ci) {
	return (int)((1 + sqrt(5)) * ci * ci / 2);
}
int main() {
	while(~scanf("%d%d",&n,&f)) {
		for(int i = 1; i <= n; i++) {
			scanf("%d",&c[i]);
		}
		for(int i = maxn - 1; i >= 0; i--) {
			dp[i] = 0;
			for(int j = 1; j <= n; j++) {
				if(i > c[j]) {
					dp[i] += cal(c[j]);
				}
				else {
					dp[i] += dp[i + c[j]] + 1;
				}
			}
			dp[i] /= n;
		}	
		printf("%.3lf\n",dp[f]);
	}
	return 0;
} 

题目5:ZOJ 3551 :Bloodsucker

题目大意:有n个人,其中1个是吸血鬼,n - 1个普通人,每天有两个人会相遇,如果是吸血鬼和人相遇,则有p的概率将这个人变成吸血鬼,问把所有人都变成吸血鬼的期望天数是几天。

解法:以吸血鬼的数量为当前状态,如果是吸血鬼和人相遇,有一定概率会多一个吸血鬼,其他情况吸血鬼数量都不会变,可以以吸血鬼数量的状态来期望dp。dp[i]表示有i个吸血鬼要使得全部人都变成吸血鬼的期望天数,显然dp[n] = 0;

对于当前有i个吸血鬼的状态转移:
算出吸血鬼和人相遇的概率 tp,则多一个吸血鬼的概率为 tp * p,其他情况吸血鬼的数量不变。
则转移方程为:dp[i] = dp[i + 1] * tp * p + dp[i] * (1 - tp * p) + 1。
发现两边都有dp[i] ,可以移项, 化简得到dp[i] = (1 + tmp * dp[i + 1]) / tmp。
采用逆推,边界状态为dp[n] = 0;
其中tp = i * (n - i) / c[n][2];

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int t,n;
double pt;
double dp[maxn];
long long c[maxn];
int main() {
	for(long long i = 1; i < maxn; i++) {
		if(i >= 2)
			c[i] = i * (i - 1) / 2;
		else c[i] = 0;
	}
	scanf("%d",&t);
	while(t--) {
		scanf("%d%lf",&n,&pt);
		dp[n] = 0;
		for(long long i = n - 1; i >= 1; i--) {
			dp[i] = 0;
			double tmp = ((double) i * (n - i) / c[n]) * pt;
			dp[i] =  (1 + tmp * dp[i + 1]) / tmp;
		}
		printf("%.3lf\n",dp[1]);
	}
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值