概率与期望DP

目录

一,概率DP

CSU 1123: PK武林盟主

CSU 1342:Double

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

二,期望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;
}
<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

二,期望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;
}

关于为什么反向求比正向求要简单的多的多:https://blog.csdn.net/nameofcsdn/article/details/52082746

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;
}

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

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

猜你喜欢

转载自blog.csdn.net/nameofcsdn/article/details/113098838