【期望】【DP】数学期望总结(ing)

题目概览
LightOJ - 1027 A Dangerous Maze
LightOJ - 1030 Discovering Gold:概率dp
LightOJ - 1038 Race to 1 Again:统计因子的贡献
LightOJ - 1248 Dice (III) :概率dp 或 几何分布
LightOJ - 1265 Island of Survival:简单O(1)题

LightOJ - 1027 A Dangerous Maze

题目链接

Description

在n个门前选择一扇门出去, 如果第i扇门的 Xi 为正,会在 Xi 时间后离开迷宫,否则会在 |Xi| 后回到初始地方,并且忘记了刚才的选择, 选择每扇门等概率的。计算离开的期望。

Solution

定义一次选择到正数的概率为P1,选择了正数后离开的平均时间为T1,选择到负数的概率是P2, 选择了负数后回到初始位置的平均时间为T2。设离开的期望为Y。
Y = P 1 ∗ T 1 + P 2 ∗ ( T 2 + Y ) Y = P1 * T1 + P2 * (T2 + Y) Y=P1T1+P2(T2+Y)
Y = P 1 ∗ T 1 + P 2 ∗ T 2 P 1 Y = \frac{P1*T1 + P2*T2}{P1} Y=P1P1T1+P2T2
经过整理 Y = ∑ i = 1 N a b s ( X i ) 正 数 的 个 数 Y=\frac{\sum _{i = 1} ^{N} abs(Xi)}{正数的个数} Y=i=1Nabs(Xi)
最后对答案取个gcd即可。

Code

#include 

#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

int main()
{
	rush() {
		int n; 
		scanf("%d", &n);
		int cnt = 0, sum = 0, x;
		for(int i = 1; i <= n; i++) {
			scanf("%d", &x);
			sum += abs(x);
			if(x > 0) cnt++;
		}
		int g = __gcd(sum, cnt);
		printf("Case %d: ", cas);
		if(cnt == 0) printf("inf\n");
		else printf("%d/%d\n", sum/g, cnt/g);
	}
	return 0;
}

LightOJ - 1030 Discovering Gold

题目链接

Description

n个点,每个点有一些金子。从点1开始,每次扔一次6面骰子,向前走点数步,并且拿走那个点的金子,如果当前位置加扔出的点数大于n,就重新扔。询问走到n时,取走的金子的期望。

Solution

方案一:从前到后递推
假设 dp[i] 为到第i个点的概率,a[i]为第i个格子的金子数量。那么期望就是 ∑ d p [ i ] ∗ a [ i ] \sum dp[i] * a[i] dp[i]a[i]
对于初始点1来说,dp[1] = 1,否则,dp[i] = dp[j] * 骰子摇出点数为 j-i 的概率

方案二:从后到前倒退
那么当前的状态等于 所有后继状态的期望 * 到达它的概率 进行求和。

由于走到的点是一定能拿到的,因此令每个的点的初始期望值等于每个点的初始值,即:dp[i] = a[i].

则:

  • 在第 n 个点,获得的金子数目为 dp[n]
  • 在第 n-1 个点,掷骰子有一种情况,获得的数目为: dp[n-1] + dp[n]
  • 在第 n-2 个点,掷骰子有两种情况,获得的数目为: dp[n-2] + dp[(n-2)+1]/2 + dp[(n-2)+2]/2
  • 在第 i 个点,掷骰子有六种情况,获得的数目为: dp[i] = dp[i] + dp[i+1]/6 + dp[i+2]/6 + dp[i+3]/6 + dp[i+1]/6 + dp[i+5]/6 + dp[i+6]/6

如上进行逆推,最后得到的 f[1] 就是最终的期望值

Code

方案一:

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

double a[105];
double dp[105], cnt[105];

int main()
{
	rush() {
		printf("Case %d: ", cas);
		int n; 
		scanf("%d", &n);
		for(int i = 1; i <= n; i++) 
			scanf("%lf", a + i);
			dp[i] = 0;
		}
		dp[1] = 1.0;
		double ans = 0.0;
		for(int i = 1; i <= n; i++) {
			for(int j = max(i - 6, 1); j < i; j++) {
				int k = min(6, n - j);
				dp[i] += dp[j] / k; // 当前点可掷出的点数共k种,其中可以由j点到达i点只有一种
			}
			ans += dp[i] * a[i];
		}
		printf("%.10f\n", ans);
	}
	return 0;
}

方案二:

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

double a[105];
double dp[105], cnt[105];

int main()
{
	rush() {
		printf("Case %d: ", cas);
		int n; 
		scanf("%d", &n);
		for(int i = 1; i <= n; i++) {
			scanf("%lf", a + i);
			dp[i] = a[i];
		}
		for(int i = n; i >= 1; i--) {
			for(int j = 1; j <= min(n - i, 6); j++) {
				dp[i] += dp[i+j] / min(n - i, 6);
			}
		}
		printf("%.10f\n", dp[1]);
	}
	return 0;
}

LightOJ - 1038 Race to 1 Again

题目链接

Description

给出一个数N,每次可以选择N的一个因子,用N除以这个因子得到一个新的数,计算 N 变为1的操作次数的期望。

Solution

求操作次数的期望时,假设第 i 个因子对当前数期望的贡献次数为 T i T_i Ti,那么有
E = ( T 1 + T 2 + T 3 + . . . . . . + T n ) / n E = (T_1 + T_2 + T_3 + ...... + T_n) / n E=(T1+T2+T3+......+Tn)/n.
dp[n]表示 n 除到 1 时的期望,假设 n 的因子有 cnt 个,分别为 a 1 , a 2 . . . a c n t a_1, a_2...a_{cnt} a1,a2...acnt(包括1和n本身),那么
d p [ n ] = ( d p [ a 1 ] + 1 ) + ( d p [ a 2 ] + 1 ) + . . . + ( d p [ a c n t ] + 1 ) c n t dp[n]=\frac{(dp[a_1]+1)+(dp[a_2]+1)+...+(dp[a_{cnt}]+1)}{cnt} dp[n]=cnt(dp[a1]+1)+(dp[a2]+1)+...+(dp[acnt]+1)
每个+1代表由 当前因子到n 的期望操作次数需要+1次,其中 a c n t a_{cnt} acnt 等于n,故 d p [ a c n t ] dp[a_{cnt}] dp[acnt] 未知,移项后整理得:,
d p [ n ] = ( d p [ a 1 ] + 1 ) + ( d p [ a 2 ] + 1 ) + . . . + ( d p [ a c n t − 1 ] + 1 ) + 1 c n t − 1 dp[n]=\frac{(dp[a_1]+1)+(dp[a_2]+1)+...+(dp[a_{cnt-1}]+1)+1}{cnt-1} dp[n]=cnt1(dp[a1]+1)+(dp[a2]+1)+...+(dp[acnt1]+1)+1

Code

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)
const int MaxN = 1e6 + 5;

double dp[MaxN];

int main()
{
	int tot, p;
	double ans;
	dp[1] = 0;
	for(int i = 2; i <= 1e5; i++) {
		tot = 0, ans = 0.0;
		for(int j = 1; j * j <= i; j++) {
			if(i % j == 0) { 
				ans += dp[j] + 1;
				tot++;
				p = i / j;
				if(p != j) {
					ans += dp[p] + 1; // 当 i/1 == i 时,由于dp[i] = 0,所以没有对答案产生贡献
					tot++;
				}
			}
		}
		dp[i] = ans / (tot - 1.0);
	}
	rush() {
		printf("Case %d: ", cas);
		int n; 
		scanf("%d", &n);
		printf("%.8f\n", dp[n]);
	}
	return 0;
}

LightOJ - 1248 Dice (III)

题目链接

Description

给定一个 n 面的骰子,计算看到所有的面至少一次所需掷骰子次数的期望

Solution

方案一:概率DP
假设 dp[i] 表示当前已经出现 i 个面时的期望次数,那么当前 dp[i] 可以由一下状态进行转移:

  • 如果当前出现的第 i 个面是新出现的面,说明当前状态的上一个状态为 dp[i-1],然后前状态 dp[i-1] 从剩下的 (n - i + 1) 个未出现的面中选取了一个作为新出现的面,此时 d p [ i ] = ( n − i + 1 ) / n ∗ d p [ i − 1 ] + 1 dp[i] = (n - i + 1) / n * dp[i-1] + 1 dp[i]=(ni+1)/ndp[i1]+1
  • 如果当前出现的第 i 个面是已经出现过的面,说明当前状态的上一个状态为 dp[i],然后前状态 dp[i] 从上个已经出现的 i-1 个面中选取了一个作为当前出现的面,此时 d p [ i ] = ( i − 1 ) / n ∗ d p [ i ] + 1 dp[i] = (i-1) / n * dp[i] + 1 dp[i]=(i1)/ndp[i]+1

综上
d p [ i ] = ( n − i + 1 ) / n ∗ d p [ i − 1 ] + ( i − 1 ) / n ∗ d p [ i ] + 1 dp[i] = (n - i + 1) / n * dp[i-1] + (i-1) / n * dp[i] + 1 dp[i]=(ni+1)/ndp[i1]+(i1)/ndp[i]+1

化简得
d p [ i ] = d p [ i − 1 ] + n / ( n − i + 1 ) dp[i] = dp[i-1] + n / (n - i + 1) dp[i]=dp[i1]+n/(ni+1)

方案二:数学 - 几何分布
几何分布的概念:
在第n次伯努利试验中,试验 k 次才得到第一次成功的机率为p。详细的说是:前k-1次皆失败,第k次成功的概率为p。

几何分布的期望 E ( X ) = 1 p E(X) = \frac{1}{p} E(X)=p1

对于本题来说:

  • 第一个面第一次出现的概率 P 1 = n n P_1=\frac{n}{n } P1=nn
  • 第二个面第一次出现的概率 P 2 = n − 1 n P_2=\frac{n-1}{n } P2=nn1
  • ……
  • 第 i 个面第一次出现的概率 P i = n − i + 1 n P_i=\frac{n-i+1}{n } Pi=nni+1
  • ……
  • 第 n 个面第一次出现的概率 P n = 1 n P_n=\frac{1}{n } Pn=n1

所以每个面至少出现一次的期望为
E ( X ) = ∑ i = 1 n 1 P i = ∑ i = 1 n n n − i + 1 E(X)=\sum_{i=1}^{n} \frac{1}{P_i}=\sum_{i=1}^{n}\frac{n}{n-i+1} E(X)=i=1nPi1=i=1nni+1n

Code

DP做法

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

double dp[100005];

int main()
{
	rush() {
		printf("Case %d: ", cas);
		int n;
		scanf("%d", &n);
		dp[1] = 1.0;
		for(int i = 2; i <= n; i++) {
			dp[i] = dp[i-1] + 1.0 * n / (n-i+1);
		}
		printf("%.8f\n", dp[n]);
	}
	return 0;
}

数学做法

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

int main()
{
	rush() {
		printf("Case %d: ", cas);
		int n;
		scanf("%d", &n);
		double ans = 0;
		for(int i = 1; i <= n; i++) {
			ans += 1.0 * n / (n - i + 1);
		}
		printf("%.8f\n", ans);
	}
	return 0;
}

LightOJ - 1265 Island of Survival

题目链接

Description

你在一个有t只老虎和d只鹿的荒岛上,计算生还几率,每天有两只动物相遇,鹿、老虎、你随机相遇:
如果你和老虎相遇,老虎会杀了你。
如果老虎和鹿相遇,老虎会吃掉鹿。
如果两只鹿见面,什么都没发生。
如果你遇见一只鹿,你可能会也可能不会杀鹿(取决于你)。
如果两只老虎相遇,他们会同归于尽。

Solution

概率DP写多了,被这题给搞了…

对你来说,威胁者只有老虎,对老虎来说,威胁者也只有老虎,所以鹿的存在对你的存活率毫无意义。

  • 如果没有老虎,不管鹿有几只,存活率都为 1
  • 如果有奇数只老虎,存活率为 0,因为不管鹿有几只,一定会有一只老虎剩下来和你相遇
  • 如果有偶数只老虎,那么需要两只老虎不停的内耗完才能存活。

对于 t 为偶数时
P = C t 2 C t + 1 2 ∗ C t − 2 2 C t − 1 2 ∗ C t − 4 2 C t − 3 2 ∗ C 2 2 C 3 2 = 1 t + 1 P = \frac{C^2_{t}}{C^2 _{t+1}} * \frac{C^2_{t-2}}{C^2 _{t-1}} * \frac{C^2_{t-4}}{C^2 _{t-3}} * \frac{C^2 _2}{C^2 _{3}} = \frac 1 {t+1} P=Ct+12Ct2Ct12Ct22Ct32Ct42C32C22=t+11

Code

#include 
#define rush() int T;scanf("%d", &T);for(int cas = 1; cas <= T; cas++)

int main()
{
	rush() {
		printf("Case %d: ", cas);
		int n, t, d;
		scanf("%d %d", &t, &d);
		double ans;
		if(t == 0) ans = 1;
		else if(t % 2 == 1) ans = 0;
		else ans = 1.0 / (t + 1);
		printf("%.8f\n", ans);
	}
	return 0;
}

你可能感兴趣的:(数学)