CodeForces 148D Bag of mice
1、UVa 11021 Tribles
题意:有k只麻球,每只只能活一天且在死前可能生出一些新麻球。它生出i个新麻球的概率分别为Pi,求在m天后,所有麻球均死亡的概率为多少(不足m天全死亡也算).
1 <= n <= 1000, 0 <= k, m <= 1000。
解法:设开始有一只麻球,m天后全部死亡的概率为f(m)。由于每个麻球独立生活,所以有:
1、所求为f(m)^k;
2、由全概率公式,f(m) = P0 + P1*f(i-1) + P2*f(i-1)^2 + P3*f(i-1)^3+....+P[n-1]*f(i-1)^(n-1)。 其中,P[k]*f(i-1)^k表示,当前麻球生了j个后代,它们在i-1天后全部死亡。
tag:math, probality, 递推
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-05 20:43 4 * File Name: math-UVa-11021.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 14 const int N = 1000; 15 16 int n, k, m; 17 double p[N+10], f[N+10]; 18 19 double gao() 20 { 21 f[0] = 0; 22 for (int i = 1; i <= m; ++ i){ 23 double tmp = 1.0; 24 f[i] = p[0]; 25 for (int j = 1; j < n; ++ j){ 26 tmp *= f[i-1]; 27 f[i] += p[j] * tmp; 28 } 29 } 30 31 double ret = 1.0; 32 for (int i = 0; i < k; ++ i) 33 ret *= f[m]; 34 return ret; 35 } 36 int main() 37 { 38 int T; 39 scanf ("%d", &T); 40 int test = 0; 41 while (T--){ 42 scanf ("%d%d%d", &n, &k, &m); 43 for (int i = 0; i < n; ++ i) 44 cin >> p[i]; 45 46 printf ("Case #%d: %.7lf\n", ++test, gao()); 47 } 48 return 0; 49 }
2、UVa 11722 Joining with Friend
题意:给出两个区间[s1, s2]和[t1, t2],要求在每个区间上任意取长度为w的一段线段(取到任何位置等可能性),求取出的两段线段有重合部分的概率。
解法:假设取出的两段线段的左端点分别为x,y,则只需要|x - y| <= w则满足题意,而x ∈ [s1, s2],y ∈ [t1, t2],这样就变成了高中数学的线性规划问题!
四个点(t1, s2), (t1, s1), (t2, s2), (t2, s1)组成一个矩形,该矩形在两直线y = x + w和y = x - w之间的部分面积为area,矩形面积s = (s2 - s1) * (t2 - t1),则所求答案为area / s。(这一步画出图就明白了)
将概率变为线性规划,真是机智!
tag:math, probability, think, good
这道题分类讨论情况很多,感觉写代码也没有什么意义,所以copy一份别人的代码......http://blog.csdn.net/shiyuankongbu/article/details/9934683
1 #include <cstdio> 2 #include <iostream> 3 #include <cmath> 4 #include <algorithm> 5 #include <cstring> 6 using namespace std; 7 double t1,t2,s1,s2,w; 8 double area(double b) 9 { 10 double s=(t2-t1)*(s2-s1); 11 double y1=t1+b; 12 double y2=t2+b; 13 if(y2<=s1) 14 return 0; 15 if(y1<=s1) 16 { 17 if(y2<=s2) 18 return 0.5*(y2-s1)*(t2-(s1-b)); 19 else 20 return 0.5*(t2-s1+b+t2-s2+b)*(s2-s1); 21 } 22 else if(y1<s2) 23 { 24 if(y2<=s2) 25 return 0.5*(t1+b-s1+t2+b-s1)*(t2-t1); 26 else 27 return s-0.5*(s2-t1-b)*(s2-t1-b); 28 } 29 else 30 return s; 31 } 32 int main() 33 { 34 int t; 35 scanf("%d",&t); 36 for(int cas=1;cas<=t;cas++) 37 { 38 scanf("%lf%lf%lf%lf%lf",&t1,&t2,&s1,&s2,&w); 39 double ans=area(w)-area(-w); 40 ans/=(s2-s1)*(t2-t1); 41 printf("Case #%d: %.8lf\n",cas,ans); 42 } 43 return 0; 44 }
3、UVa 11427 Expect the Expected
题意:有一个人打牌,每盘牌赢的几率为p,每天晚上可打n盘。当天晚上开始打牌,如果他胜利局数的比例严格大于p则今天去睡觉,明晚继续打牌。如果当天晚上打完n盘胜利局数的比例仍小于等于p,则今天去睡觉,且从今以后再也不打牌。在平均情况下,问他会打多少个晚上纸牌?(1 <= n <= 100)
解法:由于每天晚上的情况是独立的,所以先考虑每天晚上打完牌后,明晚继续打牌的概率为Q,明晚不打牌的概率为q。
设置数组f[i][j]表示第i盘牌打完后,赢了j盘的概率。
状态转移方程为if(i/j <= p) f[i][j] = f[i-1][j-1] * p + f[i-1][j] * (1-p)
else f[i][j] = f[i-1][j-1] * p(注意,若j/i > p,则在以后的状态转移方程中中均视为f[i][j] = 0,因为这盘j/i > p之后就回去睡觉,不会继续打牌了)
Q = sum(f[i][ma]) (ma表示使得ma/i > p的最小整数,此时该f[i][ma]不能视为0),q = 1 - Q。
求得q以后,可以用期望的公式求得期望。也可以用下面这种机智的方法:
设所求期望为e,则e = 1 * q + (e+1) * (1-q)
tag:math, expection, dp, probability
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-06 11:27 4 * File Name: 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 #define out(x) cout<<#x<<":"<<(x)<<endl 14 #define tst(a) cout<<#a<<endl 15 16 const int N = 100; 17 18 struct prob{ 19 int a, b; 20 double p; 21 }; 22 23 int n; 24 prob p; 25 double f[N+10][N+10]; 26 27 double DP() 28 { 29 double ret = 0; 30 CLR (f); 31 f[0][0] = 1; 32 for (int i = 1; i <= n; ++ i){ 33 int ma = p.a * i / p.b; 34 f[i][0] = f[i-1][0] * (1 - p.p); 35 for (int j = 1; j <= ma; ++ j){ 36 f[i][j] = f[i-1][j-1] * p.p + f[i-1][j] * (1-p.p); 37 } 38 ret += f[i-1][ma] * p.p; 39 } 40 return ret; 41 } 42 43 int main() 44 { 45 int T; 46 scanf ("%d", &T); 47 int test = 0; 48 while (T--){ 49 char xxx; 50 scanf ("%d%c%d%d", &p.a, &xxx, &p.b, &n); 51 p.p = (p.a+0.0) / p.b; 52 53 //q为今天玩了,以后再也不玩纸牌的概率 54 double q = 1 - DP(); 55 56 printf ("Case #%d: %d\n", ++test, (int)(1 / q)); 57 } 58 return 0; 59 }
4、UVa 11762 Race to 1
题意:给出一个正整数N,每次随机选择一个小于等于N的素数P,如果P是N的约数,则N = N / P,否则,N不变。问平均情况下,要经过多少次随机选择,才能使N变为1。(case <= 1000, 1 <= N <= 10^6)
解法:设gao(n)表示将数字n变为1所需要的平均选择次数。
对素数p(p <= n),if(n % p) gao(n) = (1 + gao(n)) / num; else gao(n) = (1 + gao(n/p)) / num;(num表示小于等于n的素数的个数)。
tag:math, expection, 递推
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-09-06 19:39 4 * File Name: 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 #include<cmath> 10 11 using namespace std; 12 13 #define CLR(x) memset(x, 0, sizeof(x)) 14 15 const double eps = 1e-8; 16 const int N = 1000000; 17 18 double f[N+1]; 19 bool vis[N+501]; 20 int prm[N+1]; 21 int all; 22 23 void sieve(int n) 24 { 25 int m = (int)sqrt(n+0.5); 26 CLR (vis); 27 for (int i = 2; i <= m; ++ i) if (!vis[i]) 28 for (int j = i*i; j <= n; j += i) vis[j] = 1; 29 } 30 31 int primes(int n) 32 { 33 sieve (n); 34 int ret = 0; 35 for (int i = 2; i <= n; ++ i) 36 if (!vis[i]) prm[ret++] = i; 37 return ret; 38 } 39 40 int bin_search(int n) 41 { 42 int l = 0, r = all - 1; 43 int mid; 44 while (l <= r){ 45 mid = (l + r) >> 1; 46 if (prm[mid] < n) l = mid + 1; 47 else r = mid - 1; 48 } 49 return l; 50 } 51 52 double gao(int n) 53 { 54 if (n == 1) return 0.0; 55 if (f[n] > eps) return f[n]; 56 if (!vis[n]) return 1 + bin_search (n); 57 58 double ret = 0.0; 59 int cnt = 0, num; 60 for (int i = 0; ; ++ i){ 61 if (prm[i] > n){ 62 num = i; 63 break; 64 } 65 66 if (n % prm[i]){ 67 ++ cnt; 68 continue; 69 } 70 ret += (1 + gao(n/prm[i])); 71 } 72 ret = (ret + cnt + 0.0) / (num - cnt); 73 return ret; 74 } 75 76 int main() 77 { 78 all = primes (N+500); 79 CLR (f); 80 81 int T; 82 scanf ("%d", &T); 83 int test = 0; 84 while (T--){ 85 int n; 86 scanf ("%d", &n); 87 printf ("Case %d: %.8lf\n", ++test, gao(n)); 88 } 89 return 0; 90 }
5、POJ 3071 FootBall
题意:给定数字n,有2^n支球队(1 ~ 2^n),第1只球队与第2只打,第3只与第4只打....获胜的球队进入下一轮,重新编号为1 ~ 2^(n-1),然后继续比赛,一直到决出冠军。给出每只球队与其他所有球队打获胜的概率,求哪只球队夺冠的可能性最大。
解法:很简单的概率DP。但这道题的亮点在于用位运算处理第i轮,j只球队可能与哪些球队打。首先,用位运算处理的话,要将球队编号为0~2^n-1,然后,将0~15的二进制形式写出来找规律,就能发现如何用位运算处理。具体见代码。
tag:math, probability, dp, 位运算
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-09 20:14 4 * File Name: math-POJ-3071.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 14 const double eps = 1e-8; 15 16 double a[150][150], p[150][150]; 17 int two[8]; 18 int n; 19 20 void init() 21 { 22 for (int i = 0; i < two[n]; ++ i) 23 for (int j = 0; j < two[n]; ++ j) 24 scanf ("%lf", &a[i][j]); 25 } 26 27 int gao() 28 { 29 CLR (p); 30 for (int i = 0; i < two[n]; ++ i) 31 p[0][i] = 1; 32 33 for (int i = 0; i < n; ++ i) 34 for (int j = 0; j < two[n]; ++ j) 35 for (int k = 0; k < two[n]; ++ k) 36 if (((j>>i)^1) == (k>>i)) 37 p[i+1][j] += p[i][j] * p[i][k] * a[j][k]; 38 39 int ret = 0; 40 for (int i = 0; i < two[n]; ++ i) 41 if (p[n][i] > p[n][ret] + eps) ret = i; 42 return ret + 1; 43 } 44 45 int main() 46 { 47 CLR (two); 48 two[0] = 1; 49 for (int i = 1; i < 8; ++ i) 50 two[i] = two[i-1] * 2; 51 while (scanf ("%d", &n) != EOF && n != -1){ 52 init(); 53 printf ("%d\n", gao()); 54 } 55 return 0; 56 }
6、POJ 3440 Coin Toss
题意:有一块长方形地板,由m*n块大小为t*t的瓷砖组成,将一个直径为c的圆放在地板上(圆心在地板长方形范围内,圆的边缘可以不在),问该圆与1,2,3,4个瓷砖的概率分别是多少。(注意保证t>c)
解法:在保证了t > c的情况下,就是一道水题,直接推公式就好。我的代码还可以再写得简单些,就是把s1, s2, s3, s4都放在一个数组里面,这样输出的时候就可以用循环输出了。
tag:math, probability
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-10 22:19 4 * File Name: math-POJ-3440.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cmath> 9 10 using namespace std; 11 12 const double PI = atan(1.0)*4; 13 14 typedef long long int64; 15 16 int main() 17 { 18 int T, test = 0; 19 scanf ("%d", &T); 20 while (T--){ 21 int64 m, n, t, c; 22 if (test) printf ("\n"); 23 printf ("Case %d:\n", ++ test); 24 scanf ("%lld%lld%lld%lld", &m, &n, &t, &c); 25 int64 s = m * n * t * t; 26 int64 s1 = m * n * (t-c) * (t-c) + (m+n) * c * (t-c) + c*c; 27 int64 s2 = (2*m*n-m-n) * c * (t-c) + (m+n-2) * c * c; 28 double s3 = (m-1) * (n-1) * c * c * (1-PI/4); 29 double s4 = (m-1) * (n-1) * PI * c * c / 4; 30 printf ("Probability of covering 1 tile = %.4f%%\n", (double)s1/(double)s * 100.0); 31 printf ("Probability of covering 2 tiles = %.4f%%\n", (double)s2/(double)s * 100.0); 32 printf ("Probability of covering 3 tiles = %.4f%%\n", s3/(double)s * 100.0); 33 printf ("Probability of covering 4 tiles = %.4f%%\n", s4/(double)s * 100.0); 34 } 35 return 0; 36 }
7、ZOJ 2949 Coins of Luck
题意:一个人吃面,可以吃A种面和B种面,每种面各有n碗,每次随机选择吃一种面,求吃完一种面的时候,吃了面的碗数的数学期望。
n <= 1000。
解法:考虑到n的范围,有两种方法。
一、dp的方法。设d[i][j]表示吃了i碗A面和j碗B面的概率。则d[i][j] = (d[i-1][j] + d[i][j-1]) / 2。
用ans[i]表示初始时有A面B面各i碗,则for j = 0 to n-1, ans[i] += 2 * (n+j) * (d[i][j] - d[i][j-1] * 0.5)。
所求即为ans[n]。
二、直接推公式的方法。记p = 0.5。for i = 0 to n-1, ans += 2 * (n+i) * C(n+i-1, i) * p^(n+i))。但是,这个公式的精度是不能被接受的,所以计算途中要用log。事先算出fac[i] = log(i!)。然后C(n, m) = fac[n] - fac[m] - fac[n-m]。总公式为ans += 2.0 * (n+i+0.0) * exp(C(n+i-1, i) + (n+i+0.0) * log(p))。(log(p)事先算出并保存)
tag:math, 概率DP
法一:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-26 00:03 4 * File Name: math-ZOJ-2949.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 10 using namespace std; 11 12 #define CLR(x) memset(x, 0, sizeof(x)) 13 14 double d[1005][1005], ans[1005]; 15 16 void DP(int n) 17 { 18 CLR (d); d[0][0] = 1.0; 19 for (int i = 1; i <= n; ++ i) 20 d[i][0] = d[0][i] = d[i-1][0] * 0.5; 21 for (int i = 1; i <= n; ++ i) 22 for (int j = 1; j <= n; ++ j) 23 d[i][j] = (d[i-1][j] + d[i][j-1]) / 2.0; 24 25 for (int i = 1; i <= n; ++ i){ 26 double sum = d[i][0] * i; 27 for (int j = 1; j < i; ++ j) 28 sum += (i+j) * (d[i][j] - d[i][j-1]*0.5); 29 ans[i] = sum * 2.0; 30 } 31 } 32 33 int main() 34 { 35 DP(1000); 36 int T; 37 scanf ("%d", &T); 38 while (T--){ 39 int n; 40 scanf ("%d", &n); 41 printf ("%.2f\n", ans[n]); 42 } 43 return 0; 44 }
法二:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-26 01:08 4 * File Name: math-HDU-4465.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cmath> 9 #include<algorithm> 10 11 using namespace std; 12 13 #define out(x) cout<<#x<<":"<<(x)<<endl 14 const int N = 2000; 15 16 double fac[N+5]; 17 18 void init(int n) 19 { 20 fac[1] = 0; 21 for (int i = 2; i <= n; ++ i) 22 fac[i] = fac[i-1] + log(i+0.0); 23 } 24 25 double C(int n, int m) 26 { 27 return fac[n] - fac[n-m] - fac[m]; 28 } 29 30 int main() 31 { 32 freopen("tst.out","w",stdout); 33 init(N); 34 int T; 35 scanf ("%d", &T); 36 while (T--){ 37 int n; 38 scanf ("%d", &n); 39 double ans = 0.0, p = log(0.5); 40 double q = log(0.5); 41 double nq = n * q, np = n * p; 42 for (int i = 0; i < n; ++ i) 43 ans += (n+i) * (exp(C(n+i-1, i) + np + i*q) + exp(C(n+i-1, i) + nq + i*p)); 44 printf ("%.2f\n", ans); 45 } 46 return 0; 47 }
8、HDU 4465 Candy
题意:有两个容器A和B,分别含有n个球,小明每次打开箱子取一个球,从A中取球的概率是p,从B中取为1-p。当某一次小明打开时,发现打开的容器没有球了,此时另一个容器有x个球。求x的数学期望。
n <= 2 * 10^5
解法:上题的第二种解法,第一种解法时间复杂度不能接受。
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-26 01:08 4 * File Name: math-HDU-4465.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cmath> 9 #include<algorithm> 10 11 using namespace std; 12 13 const int N = 200000; 14 15 double fac[N+5]; 16 17 void init(int n) 18 { 19 fac[1] = 0; 20 for (int i = 2; i <= n; ++ i) 21 fac[i] = fac[i-1] + log(i+0.0); 22 } 23 24 double C(int n, int m) 25 { 26 return fac[n] - fac[n-m] - fac[m]; 27 } 28 29 int main() 30 { 31 init(N); 32 int n, test = 0; 33 double p; 34 while (scanf ("%d%lf", &n, &p) != EOF){ 35 double q = log(1 - p), ans = 0.0; 36 p = log(p); 37 double nq = (n+1) * q, np = (n+1) * p; 38 for (int i = 0; i < n; ++ i) 39 ans += (n-i) * (exp(C(n+i, i) + np + i*q) + exp(C(n+i, i) + nq + i*p)); 40 printf ("Case %d: %.6f\n", ++test, ans); 41 } 42 return 0; 43 }
9、POJ 3744 Scout YYF I
题意:在一条路上,某人从第1格出发,每次行走有p的概率走1格,1-p的概率走2格。告诉你哪些格子有炸弹,求不踩炸弹走完这条路的概率是多少。
这条路的长度最长为10^8。
解法:分段处理,用数组x[n]表示有炸弹的地方。分段考虑1~x[0],x[0]~x[1],x[2]~x[3]....。这样,问题就变成了从第1格出发,第k格有炸弹,不踩炸弹的概率。
设d[i]表示从第一格出发,会踩到第i格的概率,则d[i] = d[i-1]*p + d[i-2]*(1-p)。但是考虑到会要求i的范围为10^8,O(n)的复杂度也不能被接受。所以,考虑用矩阵乘法快速幂来优化。这样就好了。
tag:math, 概率DP, 矩阵乘法
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-29 13:27 4 * File Name: math-POJ-3744.cpp 5 */ 6 #include<iostream> 7 #include<cstdio> 8 #include<cstring> 9 #include<algorithm> 10 11 using namespace std; 12 13 #define CLR(x) memset(x, 0.0, sizeof(x)) 14 typedef double matrix[10][10]; 15 16 int a[50], n; 17 double p; 18 matrix cnt, A; 19 20 void mtx_init() 21 { 22 cnt[0][0] = p; cnt[1][0] = 1.0 - p; 23 cnt[0][1] = 1.0; cnt[1][1] = 0.0; 24 25 A[0][0] = p*p + 1.0 - p; A[1][1] = 1.0; 26 A[0][1] = A[1][0] = p; 27 } 28 29 inline void mtx_equ(matrix& A, matrix ret) 30 { 31 for (int i = 0; i < 2; ++ i) 32 for (int j = 0; j < 2; ++ j) 33 A[i][j] = ret[i][j]; 34 } 35 36 void mtx_mul(matrix& A, matrix B) 37 { 38 matrix ret; 39 for (int i = 0; i < 2; ++ i) 40 for (int j = 0; j < 2; ++ j){ 41 ret[i][j] = 0; 42 for (int k = 0; k < 2; ++ k) 43 ret[i][j] += A[i][k] * B[k][j]; 44 } 45 46 mtx_equ(A, ret); 47 } 48 49 void mtx_pow(matrix& A, int n) 50 { 51 matrix ret; 52 CLR (ret); ret[0][0] = ret[1][1] = 1; 53 while (n){ 54 if (n & 1) mtx_mul(ret, A); 55 n >>= 1; 56 mtx_mul(A, A); 57 } 58 59 mtx_equ(A, ret); 60 } 61 62 double cal(int n) 63 { 64 if (!n || n == 1) return 1; 65 mtx_init(); 66 mtx_pow(cnt, n-2); 67 mtx_mul(A, cnt); 68 return A[0][1]; 69 } 70 71 double gao() 72 { 73 double ret = 1.0 - cal(a[0]); 74 for (int i = 1; i < n; ++ i) 75 ret *= (1.0 - cal(a[i] - a[i-1])); 76 return ret; 77 } 78 79 int main() 80 { 81 while (scanf ("%d", &n) != EOF){ 82 cin >> p; 83 for (int i = 0; i < n; ++ i) 84 scanf ("%d", &a[i]); 85 sort (a, a+n); 86 printf ("%.7f\n", gao()); 87 } 88 return 0; 89 }
10、POJ 2096 Collecting Bugs,概率DP,求期望的入门题。
11、ZOJ 3329 One Person Game,概率DP,巧妙解决带环的问题。
12、HDU 4405 Aeroplane chess,很简单的概率DP,加了一点限制条件。
13、HDU 4089 Activation,很好的概率DP题,而且是区域赛原题,2011 Beijing。
14、HDU 3853 LOOPS,概率DP,水题。
15、SGU 495 Kids and Prizes,概率DP,可以看下,与其他的题不太一样- -。
16、CodeForces 148D Bag of mice,概率DP,最普通的,加了点限制条件。
现在的你,在干什么呢?
你是不是还记得,你说你想成为岩哥那样的人。