概率与期望DP

目录

一,概率DP

CSU 1123 PK武林盟主

CSU 1342 Double

CSU 1725 加尔鲁什·地狱咆哮对阵虚灵大盗拉法姆

力扣 837. 新 21 点

力扣 808. 分汤

二,期望DP

LIghtOJ 1038 Race to 1 Again

HDU 4405 Aeroplane chess(飞行棋)

POJ 2096 Collecting Bugs

SGU 495 Kids and Prizes

三,正向推概率,反向推期望


一,概率DP

CSU 1123 PK武林盟主

题目:

Description

枫之羽认为自己很强,想当武林盟主,于是找现任武林盟主氢氧化铜挑战。氢氧化铜欣然接受了挑战,两人约好于下个月的月圆之夜在HDU校园内的三根柱子上进行决战。这场PK赛肯定能吸引武林中所有人前来观战,所以他们找了有商业运作潜力的经济人你,让你来组织这场百年一见的世纪之战,假设两人都有一定的血HP1、HP2.HP1是枫之羽的,HP2是氢氧化铜的。他们也有一定攻击力AP1、AP2,AP1是枫之羽的,AP2是氢氧化铜的。当进行攻击时,对方的HP减少自己的攻击力,比如HP1=2 HP2=1 AP1=1 AP2=1,当氢氧化铜攻击枫之羽时,枫之羽的HP=2(原先的HP1)-1(氢氧化铜的AP2)=1。现在两个人对决很多回合,每回合不是枫之羽攻击氢氧化铜,就是氢氧化铜攻击枫之羽。求枫之羽能赢氢氧化铜成为下任武林盟主的的胜率。

Input

该题含有多组测试数据,每行为HP1,HP2,AP1和AP2 (1<=HP1,HP2,AP1,AP2<=32767,都为整数,HP1/AP2<=1000&&HP2/AP1<=1000)

Output

每组数据输出一行,为枫之羽赢氢氧化铜概率的值 (结果保留4位小数).

Sample Input

2 1 1 1

Sample Output

75.0000

思路:概率DP

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
 
double ans[1005][1005];
 
double f(int x, int y)
{
	if (x <= 0)return 0;
	if (y <= 0)return 100;
	if (ans[x][y]>=0)return ans[x][y];
	return ans[x][y] = (f(x - 1, y) + f(x, y - 1)) / 2;
}
 
int main()
{
	int hp1, hp2, ap1, ap2;
	while (scanf("%d%d%d%d",&hp1,&hp2,&ap1,&ap2)!=EOF)
	{
		hp1 = (hp1 + ap2 - 1) / ap2;
		hp2 = (hp2 + ap1 - 1) / ap1;
		for (int i = 0; i <= hp1; i++)
		for (int j = 0; j <= hp2; j++)ans[i][j] = -1;
		printf("%.4f\n", f(hp1, hp2));
	}
	return 0;
}

CSU 1342 Double

题目:

Description

    有一个由M个整数组成的序列,每次从中随机取一个数(序列中每个数被选到的概率是相等的)累加,一共取N次,最后结果能被3整除的概率是多少?

Input

    输入包含多组数据。
    对于每组测试数据,第一行包含两个整数MN (1 <= M <= 100, 1 <= N <= 30),含义同上。接下来一行包含M个在[1, 100]范围内整数,依次描述了这个序列中的各个整数。

Output

    对于每组数据,用“Case XY”的格式输出答案,其中X表示是第几组测试数据(从1开始),Y表示最后结果能被3整除的概率,四舍五入保留8位小数。

Sample Input

2 2
1 2
1 2
4
1 3
4
5 10
4 5 3 1 5

Sample Output

Case 1: 0.50000000
Case 2: 0.00000000
Case 3: 1.00000000
Case 4: 0.33333340

Hint

    这个题目主要是想推荐大家用“double”处理浮点数,而尽量不要用“float”,因为“float”的精度偏低,往往不能满足题目的精度要求,所以在ACM竞赛中索性可以直接使用“double”去处理浮点数问题。“double”用“%lf”控制读入,“%f”控制输出。
    我们先解决一下其他的细节问题再来讨论这个题的思路。
    首先,这个题目也是有多组数据的,但不像“A Sample Problem”那样直接给出了数据的组数,那么要怎么处理呢?这时我们一般采用类似while(scanf(“%d%d”, &M, &N) != EOF){}这样的代码来处理,“!= EOF”是关键,至于他的具体含义就不过多介绍了,总之有了这个框架之后,我们直接在while循环里面写我们处理每组数据的代码就可以了。这时你可能会有这样的疑问:如果这么写代码的话,那么我在手动输入样例的时候怎么才算结束输入呢?Ctrl + Z,然后回车就OK了!
    其次,保留8位小数怎么处理呢?一般在ACM竞赛里面,如果没有明确说明具体怎么处理(比如要用“去尾法”),或者说让“四舍五入”,我们都采用类似printf(“%.8f”, x)的形式保留指定位数的小数。至于使用C++中的cout输出的同学,请自己查阅控制小数位数的相关资料。
    接下来我们就分析这个题目怎么做吧。
    首先,最后能不能被3整除,实际上和最后取出的整数之和(不妨记这个和为“S”)模3的结果有关系。所谓的“模”就是C/C++中的运算符“%”,就是“除以某个数取余”的意思。如果S%3==0,那么就是能被3整除,如果S%3==1或者S%3==2,那么就不能被3整除。也就是说我们要计算的就是S%3==0的概率。
    我们不妨分析一下第四个样例。取十次有点多,我们先取一次看看。
    取一次的话,模3得0的数只有3,所以S%3==0的概率就是0.2(记这个概率为p0),模3得1的数有两个:4和1,所以S%3==1的概率就是0.4(记这个概率为p1),同样的,模3得2的数有两个:5和5,所以S%3==2的概率也是0.4(记这个概率为p2)。
    再分析一下取两次的情况?
    取两次的话S%3==0的概率要怎么算呢?p0*p0 + p1*p2 + p2*p1 = 0.36。这么算的含义是什么呢?因为要保证最后S%3==0,那么如果第一次取出的数模3得0的话,第二次取的数必须也是模3得0,同样的,如果第一次取出的数模3得1的话,那么第二次取出的数必须是模3得2,如果第一次取出的数模3得2的话,那么第二次取出的数必须模3得1。这样我们就得到了上面的式子。同理,我们可以计算S%3==1的概率为p0*p1 + p1*p0 + p2*p2 = 0.32,S%3==2的概率也是0.32。
    再分析一下取三次的情况?
    取三次的话S%3==0的概率应当是0.36*p0 + 0.32*p2 + 0.32*p1。这么算的意义想必大家已经想到了,因为我们要保证最后S%3==0,那么如果前两次取出的数之和模3得0的话,那么第三次取出的数必须也是模3得0,同样的,如果前两次取出的数之和模3得1的话,那么第三次取出的数必须是模3得2,如果前两次取出的数之和模3得2的话,那么第三次取出的数必须是模3得1。同理,我们也很容易写出S%3==1以及S%3==2的概率要怎么算。
    分析到这里想必大家应该已经想到第四次,第五次,一直到第十次要怎么计算了吧?用类似的办法,根据第三次的结果就可以计算出第四次的结果,根据第四次的结果就可以算出第五次的,等等。这样即使一直算到一百次也不成问题!for循环100次就OK了。

// 以上都是hint

其实这个题目可以算是概率DP。代码估计都差不多,我用了一点空间优化,然而并没有什么区别。

代码:

#include <iostream>
#include<iomanip>
using namespace std;

double p1, p2, p3;
int a, b, c;

void get_p()
{
	int s = a + b + c;
	double x1 = a*p3 + b*p2 + c*p1;
	double x2 = b*p3 + c*p2 + a*p1;
	p3 = (a*p2 + b*p1 + c*p3) / s;
	p2 = x2 / s;
	p1 = x1 / s;
}

int main()
{
	ios_base::sync_with_stdio(false);
	int m, n, i = 1, num;
	while (cin >> m >> n)
	{
		a = 0, b = 0, c = 0;
		while (m--)
		{
			cin >> num;
			if (num % 3 == 1)a++;
			else if (num % 3 == 2)b++;
			else c++;
		}
		p1 = 0, p2 = 0, p3 = 1;
		while (n--)get_p();
		cout << "Case " << i++ << ": " << fixed << setprecision(8) << p3 << endl;
	}
	return 0;
}

CSU 1725 加尔鲁什·地狱咆哮对阵虚灵大盗拉法姆

题目:

Description

加尔鲁什·地狱咆哮看虚灵大盗拉法姆不顺眼已经很久了,终于一天,加尔鲁什·地狱咆哮带着自己的手下恐怖的奴隶主堵住了虚灵大盗拉法姆,虚灵大盗拉法姆拉法姆见势不妙,掏出神器死亡丧钟准备还击。场面十分壮观,如图(略)

已知恐怖丧钟每一发击中敌方单位都将令对方的生命值减一,且击中每个敌方单位的概率是相等的。而恐怖的奴隶主有3点生命值,当其在场上受到非致命伤害且场上恐怖的奴隶主总数小于7时会召唤一个新的3点生命值的恐怖的奴隶主,受到致命伤害(受到攻击后生命为0)时则会直接死去。如场上有1个生命值为3的“恐怖的奴隶主”,当恐怖丧钟打中他时,他的生命值变为2,且召唤一个新的奴隶主。而当恐怖丧钟击中加尔鲁什时,恐怖的奴隶主只会强力围观却什么都不会做。

现知,恐怖丧钟共计会发射X次,加尔鲁什有Y点体力值,而他手下共有Z名生命值为三的奴隶主。问在死亡丧钟使用完毕后,有多大的概率杀死加尔鲁什?(答案保留小数点后6位)

Input

多组数据,第一行有一个整数T,表示有T组数据。(T<=100)
以下T行,每行有三个整数X,Y和Z。(1<=X,Y<=20,0<=Z<=7)

Output

一个小数(保留小数点后六位)。

Sample Input

4
1 1 1
2 1 1
2 1 2
2 2 2

Sample Output

0.500000
0.666667
0.500000
0.111111

这个题目思路倒不复杂,就是记忆化搜索。

用了一个很有趣的东西来简化了一点点代码:(z1 + z2 + z3 < 7)的值是1或者0,true对应1,false对应0。

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
double list[21][21][8][8][8];
 
double f(int x, int y, int z1, int z2, int z3)
{
	if (list[x][y][z1][z2][z3] >= 0)return list[x][y][z1][z2][z3];
	if (y == 0)return 1;	
	if (x < y)return 0;
	double p1 = f(x-1, y - 1, z1, z2, z3);
	double p2 = 0, p3 = 0, p4 = 0;
	if(z1)p2 = f(x-1, y, z1 - 1, z2, z3);
	if(z2)p3 = f(x-1, y, z1 + 1, z2 - 1, z3 + (z1 + z2 + z3 < 7));
	if(z3)p4 = f(x-1, y, z1, z2 + 1, z3 - 1 + (z1 + z2 + z3 < 7));
	list[x][y][z1][z2][z3] = (p1 + p2*z1 + p3*z2 + p4*z3) / (1 + z1 + z2 + z3);
	return list[x][y][z1][z2][z3];
}
 
int main()
{
	int cas;
	cin >> cas;
	int x, y;
	int z1, z2, z3;
	while (cas--)
	{
		cin >> x >> y >> z3;
		z1 = z2 = 0;
		memset(list, -1, sizeof(list));
		cout << fixed << setprecision(6) << f(x, y, z1, z2, z3) << endl;
	}
	return 0;
}

因为 f 写的很精确,几乎没法再变快了,所以这个题目0ms完美AC

力扣 837. 新 21 点

爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:

爱丽丝以 0 分开始,并在她的得分少于 k 分时抽取数字。 抽取时,她从 [1, maxPts] 的范围中随机获得一个整数作为分数进行累计,其中 maxPts 是一个整数。 每次抽取都是独立的,其结果具有相同的概率。

当爱丽丝获得 k 分 或更多分 时,她就停止抽取数字。

爱丽丝的分数不超过 n 的概率是多少?

与实际答案误差不超过 10-5 的答案将被视为正确答案。

 
示例 1:

输入:n = 10, k = 1, maxPts = 10
输出:1.00000
解释:爱丽丝得到一张牌,然后停止。
示例 2:

输入:n = 6, k = 1, maxPts = 10
输出:0.60000
解释:爱丽丝得到一张牌,然后停止。 在 10 种可能性中的 6 种情况下,她的得分不超过 6 分。
示例 3:

输入:n = 21, k = 17, maxPts = 10
输出:0.73278
 

提示:

0 <= k <= n <= 104
1 <= maxPts <= 104

class Solution {
public:
	double new21Game(int n, int k, int maxPts) {
		if (k == 0)return 1;
		vector<double>p(k);
		p[0] = 1;
		double ans = 0;
		for (int i = 0; i < k; i++) {
			double x = p[i] / maxPts;
			for (int j = i + 1; j <= i + maxPts && j <k; j++)p[j] +=x;
			if (i + maxPts >= k)ans += (min(n, i + maxPts) - k + 1)*x;
		}		
		return ans;
	}
};

力扣 808. 分汤

有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作:

  1. 提供 100ml 的 汤A 和 0ml 的 汤B 。
  2. 提供 75ml 的 汤A 和 25ml 的 汤B 。
  3. 提供 50ml 的 汤A 和 50ml 的 汤B 。
  4. 提供 25ml 的 汤A 和 75ml 的 汤B 。

当我们把汤分配给某人之后,汤就没有了。每个回合,我们将从四种概率同为 0.25 的操作中进行分配选择。如果汤的剩余量不足以完成某次操作,我们将尽可能分配。当两种类型的汤都分配完时,停止操作。

注意 不存在先分配 100 ml 汤B 的操作。

需要返回的值: 汤A 先分配完的概率 +  汤A和汤B 同时分配完的概率 / 2。返回值在正确答案 10-5 的范围内将被认为是正确的。

示例 1:

输入: n = 50
输出: 0.62500
解释:如果我们选择前两个操作A 首先将变为空。
对于第三个操作,A 和 B 会同时变为空。
对于第四个操作,B 首先将变为空。
所以 A 变为空的总概率加上 A 和 B 同时变为空的概率的一半是 0.25 *(1 + 1 + 0.5 + 0)= 0.625。

示例 2:

输入: n = 100
输出: 0.71875

提示:

  • 0 <= n <= 109
class Solution {
public:
    double soupServings(int n) {
        if(n>8000)return 1;
        n=(n+24)/25;        
        double p1,p2;
        dp(n,n,p1,p2);
        return p1+p2/2;
    }
    void dp(int a,int b,double &p1,double &p2){
        p1=p2=0;
        if(m1[a][b]>0){
            p1=m1[a][b];
            p2=m2[a][b];
        }
        else if(a<=0 && b<=0)p2=1;
        else if(a<=0)p1=1;
        else if(b<=0)p1=0;
        else{
            double p3,p4;
            dp(a-4,b,p3,p4);
            p1+=p3,p2+=p4;
            dp(a-3,b-1,p3,p4);
            p1+=p3,p2+=p4;
            dp(a-2,b-2,p3,p4);
            p1+=p3,p2+=p4;
            dp(a-1,b-3,p3,p4);
            p1+=p3,p2+=p4;
            p1/=4,p2/=4;
            m1[a][b]=p1;
            m2[a][b]=p2;
        }
    }
    map<int,map<int,double>>m1;
    map<int,map<int,double>>m2;
};

二,期望DP

LIghtOJ 1038 Race to 1 Again

题目:

Description

Rimi learned a new thing about integers, which is - any positive integer greater than 1 can be divided by its divisors. So, he is now playing with this property. He selects a number N. And he calls this D.

In each turn he randomly chooses a divisor of D (1 to D). Then he divides D by the number to obtain new D. He repeats this procedure until Dbecomes 1. What is the expected number of moves required for N to become 1.

Input

Input starts with an integer T (≤ 10000), denoting the number of test cases.

Each case begins with an integer N (1 ≤ N ≤ 105).

Output

For each case of input you have to print the case number and the expected value. Errors less than 10-6 will be ignored.

Sample Input

3

1

2

50

Sample Output

Case 1: 0

Case 2: 2.00

Case 3: 3.0333333333

刚开始我以为是把N分解成k个素数的积,然后答案就是关于k的函数,而且这个函数可以事先自己求完存成数组。

然而,我发现给的例子我算的不对!

原来,不同的素数被选中的概率是不一样的。。。

50=2*5*5,不是3个素数独立的选择,而是按照50的约数等概率的选择。

我算了一下,不超过10^5的数最多有6个不同的素因子,所以答案可以表示成关于这6个素因子的次数的函数ff

如果不足6个,后面的几个变量自动是0

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
double r[17][10][7][5][5][5];
int list[6];
double ff(int a, int b, int c, int d, int e, int f)
{
	if (a + b + c + d + e + f == 0)return 0;
	if (r[a][b][c][d][e][f] >= 0)return r[a][b][c][d][e][f];
	r[a][b][c][d][e][f] = 0;
	for (int i1 = a; i1 >= 0; i1--)
	for (int i2 = b; i2 >= 0; i2--)
	for (int i3 = c; i3 >= 0; i3--)
	for (int i4 = d; i4 >= 0; i4--)
	for (int i5 = e; i5 >= 0; i5--)
	for (int i6 = f; i6 >= 0; i6--)
		r[a][b][c][d][e][f] += ff(i1, i2, i3, i4, i5, i6);
	int k = (a + 1)*(b + 1)*(c + 1)*(d + 1)*(e + 1)*(f + 1);
	r[a][b][c][d][e][f] += k;
	r[a][b][c][d][e][f] /= (k - 1);
	return r[a][b][c][d][e][f];
}
 
bool prime(int m)
{
	for (int i = 2; i*i <= m; i++)if (m%i == 0)return false;
	return true;
}
 
int main()
{
	int t;
	int n;
	cin >> t;
	memset(r, -1, sizeof(r));
	for (int cas = 1; cas <= t;cas++)
	{
		cin >> n;
		if (n == 1)
		{
			cout << "Case " << cas << ": " << 0 << endl;
			continue;
		}
		
		memset(list, 0, sizeof(list));
		int k = 0;
		for (int i = 2; i <= n; i++)
		{
			if (i + i > n)i = n;
			if (prime(i) && n%i == 0)
			{
				while (n%i == 0)
				{
					list[k]++;
					n /= i;
				}
				k++;
			}
		}
		cout <<"Case "<<cas<<": "<<fixed<<setprecision(8)
		<<ff(list[0],list[1],list[2],list[3],list[4],list[5]) << endl;
	}
	return 0;
}

可惜居然超时了。

仔细一想,我这个算法,算出来100之后,再算36应该非常快,直接在6维数组里面取就可以了(输入100和36答案是一样的)

不过每个数都要素因数分解倒是有些花时间。

如果只是找n的所有因子,而不需要判断因子是不是素数,应该会快一些。

果然,用最普通的方法写,就过了

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
double r[100001];
 
double f(int m)
{
	if (r[m] >= 0)return r[m];
	int k = 0;
	r[m] = 0;
	for (int i = 2; i * i <= m; i++)
	{
		if (m%i == 0)
		{
			r[m] += f(i);
			k++;
			if (i*i != m)
			{
				r[m] += f(m / i);
				k++;
			}
		}
	}
	r[m] += k + 2;
	r[m] /= k + 1;
	return r[m];
}
 
int main()
{
	int t;
	int n;
	cin >> t;
	memset(r, -1, sizeof(r));
	r[1] = 0;
	for (int cas = 1; cas <= t;cas++)
	{
		cin >> n;
		cout << "Case " << cas << ": " << fixed << setprecision(8) << f(n) << endl;
	}
	return 0;
}

HDU 4405 Aeroplane chess(飞行棋)

题目:

Description

Hzz loves aeroplane chess very much. The chess map contains N+1 grids labeled from 0 to N. Hzz starts at grid 0. For each step he throws a dice(a dice have six faces with equal probability to face up and the numbers on the faces are 1,2,3,4,5,6). When Hzz is at grid i and the dice number is x, he will moves to grid i+x. Hzz finishes the game when i+x is equal to or greater than N. 

There are also M flight lines on the chess map. The i-th flight line can help Hzz fly from grid Xi to Yi (0<Xi<Yi<=N) without throwing the dice. If there is another flight line from Yi, Hzz can take the flight line continuously. It is granted that there is no two or more flight lines start from the same grid. 

Please help Hzz calculate the expected dice throwing times to finish the game. 

Input

There are multiple test cases. 
Each test case contains several lines. 
The first line contains two integers N(1≤N≤100000) and M(0≤M≤1000). 
Then M lines follow, each line contains two integers Xi,Yi(1≤Xi<Yi≤N).   
The input end with N=0, M=0. 

Output

For each test case in the input, you should output a line indicating the expected dice throwing times. Output should be rounded to 4 digits after decimal point. 

Sample Input

2 0
8 3
2 4
4 5
7 8
0 0

Sample Output

1.1667
2.3441

这个题目还是有点复杂的。

不过有一个地方可以化简,那就是,因为我们基本上都是用累加刷表的形式来算的,所以如果有2条飞行线(flight line)连起来,这个是不用考虑的。

也就是说,我们就当没有这种现象,来写代码就可以了,如果有这种现象出现,答案也是一样的。

我的第一种思路是,用list记录到底某个格子的概率,用times记录从0到这个格子所需要的平均次数。

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
int n, m;
double list[100011];		//概率
double times[100011];		//平均次数
int fly[100011];		//飞行线
 
double f(int i,int k)
{
	if (i < 0)return 0;
	if (i >= n - 5 && k == n)return list[i] * (7 - n + i) / 6;
	return list[i] / 6;
}
 
double ft(int i,int k)
{
	if (i < 0)return 0;
	return f(i, k) * (times[i] + 1);
}
 
int main()
{
	int start, end_;
	double r1, r2;
	while (cin >> n >> m)
	{		
		if (n == 0)break;
		memset(fly, 0, sizeof(fly));
		for (int i = 0; i < m; i++)
		{
			cin >> start >> end_;
			fly[start] = end_;
		}
		memset(list, 0, sizeof(list));
		memset(times, 0, sizeof(times));
		list[0] = 1;
		times[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			list[i] += f(i - 1, i) + f(i - 2, i) + f(i - 3, i) + f(i - 4, i) + f(i - 5, i) + f(i - 6, i);
			times[i] += ft(i - 1, i) + ft(i - 2, i) + ft(i - 3, i) + ft(i - 4, i) + ft(i - 5, i) + ft(i - 6, i);
			if (list[i] > 0)times[i] /= list[i];
			if (fly[i])
			{
				list[fly[i]] += list[i];
				times[fly[i]] += list[i] * times[i];
				list[i] = 0;
			}
		}		
		cout << fixed << setprecision(4) << times[n] << endl;
	}
	return 0;
}

这个是78ms的

因为在AC之前出现过几次wrong answer,我以为是除0的问题,所以在这个代码完成之前我是用另外一个代码AC的。

我的第二种思路:times不表示次数,而表示次数的期望。

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
int n, m;
double list[100011];		//概率
double times[100011];		//次数的期望
int fly[100011];		//飞行线
 
double f(int i,int k)
{
	if (i < 0)return 0;
	if (i >= n - 5 && k == n)return list[i] * (7 - n + i) / 6;
	return list[i] / 6;
}
 
double ft(int i,int k)
{
	if (i < 0)return 0;
	if (list[i] == 0)return 0;
	if (i >= n - 5 && k == n)return (times[i] + list[i])*(7 - n + i) / 6;
	return (times[i] + list[i]) / 6;
}
 
int main()
{
	int start, end_;
	double r1, r2;
	while (cin >> n >> m)
	{		
		if (n == 0)break;
		memset(fly, 0, sizeof(fly));
		for (int i = 0; i < m; i++)
		{
			cin >> start >> end_;
			fly[start] = end_;
		}
		memset(list, 0, sizeof(list));
		memset(times, 0, sizeof(times));
		list[0] = 1;
		times[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			list[i] += f(i - 1, i) + f(i - 2, i) + f(i - 3, i) + f(i - 4, i) + f(i - 5, i) + f(i - 6, i);
			times[i] += ft(i - 1, i) + ft(i - 2, i) + ft(i - 3, i) + ft(i - 4, i) + ft(i - 5, i) + ft(i - 6, i);
			if (fly[i])
			{
				list[fly[i]] += list[i];
				times[fly[i]] += times[i];
				list[i] = 0;
			}
		}		
		cout << fixed << setprecision(4) << times[n] << endl;
	}
	return 0;
}

这个代码最后一次的修改是最后的 if 里面的代码。AC是140ms。

因为2个代码几乎是差不多的,所以我把前面那个代码也对应的改了交了AC了。(本文的3个代码都是AC了的)

在我写到这里的时候,惊奇的发现,求期望我居然是从0往后顺着推的!

明明应该从n往前倒推啊啊啊。。。

然后我又写了一下逆推的算法,只写了2分钟就写完了,提交直接AC,而且只有31ms。逆推比顺推简单太多太多了。

这里的times的意思又不一样,指的是从每个格子到底终点所需要的平均次数。

代码:

#include<iostream>
#include<string.h>
#include<iomanip>
using namespace std;
 
int n, m;
double times[100011];		//平均次数
int fly[100011];		//飞机
 
int main()
{
	int start, end_;
	double r1, r2;
	while (cin >> n >> m)
	{
		if (n == 0)break;
		memset(fly, 0, sizeof(fly));
		for (int i = 0; i < m; i++)
		{
			cin >> start >> end_;
			fly[start] = end_;
		}
		memset(times, 0, sizeof(times));
		for (int i = n - 1; i >= 0; i--)
		{
			if (fly[i])times[i] = times[fly[i]];
			else times[i] = (times[i + 1] + times[i + 2] + times[i + 3] + times[i + 4] + times[i + 5] + times[i + 6]) / 6 + 1;
		}
		cout << fixed << setprecision(4) << times[0] << endl;
	}
	return 0;
}

关于为什么反向求比正向求要简单的多的多,看下文。

POJ 2096 Collecting Bugs

题目:

Description

Ivan is fond of collecting. Unlike other people who collect post stamps, coins or other material stuff, he collects software bugs. When Ivan gets a new program, he classifies all possible bugs into n categories. Each day he discovers exactly one bug in the program and adds information about it and its category into a spreadsheet. When he finds bugs in all bug categories, he calls the program disgusting, publishes this spreadsheet on his home page, and forgets completely about the program. 
Two companies, Macrosoft and Microhard are in tight competition. Microhard wants to decrease sales of one Macrosoft program. They hire Ivan to prove that the program in question is disgusting. However, Ivan has a complicated problem. This new program has s subcomponents, and finding bugs of all types in each subcomponent would take too long before the target could be reached. So Ivan and Microhard agreed to use a simpler criteria --- Ivan should find at least one bug in each subsystem and at least one bug of each category. 
Macrosoft knows about these plans and it wants to estimate the time that is required for Ivan to call its program disgusting. It's important because the company releases a new version soon, so it can correct its plans and release it quicker. Nobody would be interested in Ivan's opinion about the reliability of the obsolete version. 
A bug found in the program can be of any category with equal probability. Similarly, the bug can be found in any given subsystem with equal probability. Any particular bug cannot belong to two different categories or happen simultaneously in two different subsystems. The number of bugs in the program is almost infinite, so the probability of finding a new bug of some category in some subsystem does not reduce after finding any number of bugs of that category in that subsystem. 
Find an average time (in days of Ivan's work) required to name the program disgusting.

Input

Input file contains two integer numbers, n and s (0 < n, s <= 1 000).

Output

Output the expectation of the Ivan's working days needed to call the program disgusting, accurate to 4 digits after the decimal point.

Sample Input

1 2

Sample Output

3.0000

相当于求max(X,Y)的期望,其中X和Y都是超几何分布。

不过,用数学方法求解是非常难的(貌似是可以求的,不过组合的计算实在是复杂)

这个题目要用递推式来求解:

代码:

#include<iostream>
#include<iomanip>
#include<string.h>
using namespace std;
 
int n, s;
double list[1001][1001];
 
double f(int i, int j)
{
	if (list[i][j] >= 0)return list[i][j];
	if (i > n || j > s)return 0;
	double d = n*s;
	d += i*(s - j)*f(i, j + 1);
	d += (n - i)*j*f(i + 1, j);
	d += (n - i)*(s - j)*f(i + 1, j + 1);
	list[i][j] = d / (n*s - i*j);
	return list[i][j];
}
 
int main()
{	
	cin >> n >> s;
	memset(list, -1, sizeof(list));
	list[n][s] = 0;
	cout << fixed << setprecision(4) << f(0, 0);
	return 0;
}

SGU 495 Kids and Prizes

题目:

Description

ICPC (International Cardboard Producing Company) is in the business of producing cardboard boxes. Recently the company organized a contest for kids for the best design of a cardboard box and selected  M winners. There are  N prizes for the winners, each one carefully packed in a cardboard box (made by the ICPC, of course). The awarding process will be as follows:

  • All the boxes with prizes will be stored in a separate room.
  • The winners will enter the room, one at a time.
  • Each winner selects one of the boxes.
  • The selected box is opened by a representative of the organizing committee.
  • If the box contains a prize, the winner takes it.
  • If the box is empty (because the same box has already been selected by one or more previous winners), the winner will instead get a certificate printed on a sheet of excellent cardboard (made by ICPC, of course).
  • Whether there is a prize or not, the box is re-sealed and returned to the room.

The management of the company would like to know how many prizes will be given by the above process. It is assumed that each winner picks a box at random and that all boxes are equally likely to be picked. Compute the mathematical expectation of the number of prizes given (the certificates are not counted as prizes, of course).

Input

The first and only line of the input file contains the values of  N and  M  ( ).

Output

The first and only line of the output file should contain a single real number: the expected number of prizes given out. The answer is accepted as correct if either the absolute or the relative error is less than or equal to 10^-9.

Example(s)

sample input
sample output
5 7
3.951424
sample input
sample output
4 3
2.3125

这个题目就是说,m个人独立的选n个盒子(奖品),求平均会选到多少个盒子。

我记得算法导论上面那个地方是有讲这个问题的,考虑1个盒子被选中的概率。

对于1个盒子,1个人不选它的概率是1-1.0/n,所以m个人都不选它的概率是(1-1.0/n)^m.

所以1个盒子平均会被选中1-(1-1.0/n)^m个。

因为所有的盒子都是一样的,所以答案就是(1-(1-1.0/n)^m)*n

代码:

#include<iostream>
#include<iomanip>
using namespace std;
 
int main()
{
	int n, m;
	cin >> n >> m;
	double a = 1 - 1.0 / n;
	double b = 1;
	while (m--)b *= a;
	cout << fixed<<setprecision(10)<<n*(1 - b);
	return 0;
}

这个代码。。。实在是短的可怕

当然,也可以用动态规划来做,不过没有必要。

三,正向推概率,反向推期望

这个思想真是太经典了!

为什么要正向推概率,反向推期望呢?

首先,我们看看什么是条件概率

事件A在另外一个事件B已经发生条件下的发生概率。条件概率表示为P(A|B),读作“在B条件下A的概率”。

然后,我们再看看什么是贝叶斯公式

算了,我就不扯了,自己点进去看吧。

现在,正文来了。

拿飞行棋来说,可以先看看HDU 4405 Aeroplane chess 概率与期望DP_nameofcsdn的博客-CSDN博客

我写了3种方法算从0到n的平均步数,很明显,最后那个逆推的算法比前面2个顺推的算法要简单的多。

举个例子,假设n=20,m=0(即没有飞行线)

先看倒推。假设你已经在第10格了,那么你下面会有6种情况,11,12,13,14,15,16,你会以各自1.0/6的概率到底每一个格子。

所以
times[i] = (times[i + 1] + times[i + 2] + times[i + 3] + times[i + 4] + times[i + 5] + times[i + 6]) / 6 + 1

再看看顺推有什么不一样???

假设你想要到达第10格,那么你仍然有6种方案,从4到10,从5到10,从6到10,从7到10,从8到10,从9到10,

但是这6种方案的概率不是1.0/6  !

为什么会这样呢?

因为,飞行棋我们都知道,有些格子是到不了的,就是说你跳过去了,没有经过它。

所以说,能够到达每个格子的概率都是一个常数,但是这个常数很复杂,几乎没什么规律。

简单的说,假设能够到达4的概率是0.99,能够到达5,6,7,8,9的概率都是0.0001,

再假设从0到4平均要2步,从0到5,6,7,8,9的平均步数都是4步(请忽略我这个假设能否对应到真实的情况)

那么,到达10之前很可能就是从4跳过来的,那么到达10的平均步数就是大约3。

对于这种结局一定的游戏,求期望应该倒推(我认为我已经说清楚了为什么)

对于有些游戏,结局是无法预判的,只能说结局是某种情形的概率是多少,这种情况自然只能顺推了。

例如 CSU 1725 加尔鲁什·地狱咆哮对阵虚灵大盗拉法姆

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
期望dp概率dp是两种不同的动态规划方法。 期望dp是指通过计算每个状态的期望值来求解最终的期望。在期望dp中,我们通常定义dp\[i\]表示在第i个状态时的期望值,然后通过状态转移方程来更新dp数组,最终得到最终状态的期望值。期望dp通常用于求解期望问题,例如求解骰子的期望点数、求解抽奖的期望次数等。 概率dp是指通过计算每个状态的概率来求解最终的概率。在概率dp中,我们通常定义dp\[i\]表示在第i个状态时的概率,然后通过状态转移方程来更新dp数组,最终得到最终状态的概率概率dp通常用于求解概率问题,例如求解抛硬币出现正面的概率、求解从一副牌中抽到红心的概率等。 总结来说,期望dp概率dp的区别在于它们所计算的是不同的值,期望dp计算的是期望值,而概率dp计算的是概率值。 #### 引用[.reference_title] - *1* [概率/期望dp专题](https://blog.csdn.net/qq_34416123/article/details/126585094)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【动态规划】数学期望/概率DP/期望DP详解](https://blog.csdn.net/weixin_45697774/article/details/104274160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值