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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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 }
View Code

法二:

 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 }
View Code

 

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 }
View Code

 

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 }
View Code

 

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,最普通的,加了点限制条件。

 

 

 

posted @ 2013-09-05 21:20  Plumrain  阅读(423)  评论(0编辑  收藏  举报