[kuangbin带你飞]专题二十一 概率&期望 部分题解
2018-03-08 18:02:20
A - A Dangerous Maze LightOJ - 1027
题意: 小花的迷宫入口有n个门 走每个门的概率都是相同的 每个门有一个值x x可正可负 正代表x 分钟后可以征服迷宫 负代表|x|分钟之后小花会回到原点 问征服迷宫时间的期望
题解:假设小花平均E分钟可以征服迷宫
如果小花刚开始选择了一个值为负的门 那么此时的时间变成了E+|Xi| 概率为1/n 因为选择每个门的概率都相同 PS:相当于在原来可以出去的基础上浪费了|Xi|分钟
如果小花刚开始选择了一个值为正的门 那么此时的时间变成了Xi 概率为1/n 因为选择每个门的概率都相同
假设值为正的门有n0个 值为负的门有n1个 正的门的值的总和为T0 负的门的值的总和为|T1|
则 E = T0/n + (E+|Xi|)/n*n1
= T0/n + |T1|/n + E/n*n1
n0/n*E = (T0+|T1|)/n
E = (T0 + |T1|) / n0
刚接触概率与期望 感谢http://blog.csdn.net/jinglinxiao/article/details/70226889
代码如下:
#include<stdio.h> #include<iostream> #include<algorithm> using namespace std; int t; int n , a[110]; int num , tmp , sum; int gcd(int a , int b)//最大公因数 { return b==0?a:gcd(b , a%b); } int main() { scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%d" , &n); num = 0; sum = 0; for(int i=0; i<n; i++) { scanf("%d" , &tmp); sum += abs(tmp); if(tmp < 0) continue; num++; } if(num == 0) { printf("Case %d: inf\n" , cas); continue; } tmp = gcd(sum , num); printf("Case %d: %d/%d\n" , cas , sum/tmp , num/tmp); } return 0; }
B - Discovering Gold LightOJ - 1030
题意: 小花进入到了一个1*n的山洞 n个单元格标号为1~n 刚开始小花在1号点 你可以想象为在玩飞行棋 走之前需要先掷骰子 掷出来的数字是几就走几步 如果超过了山洞的长度就重新掷 每个单元格上都有一定数量 对的黄金 问能够得到黄金的期望
题解: 这道题我是先求出了到达每个点的概率 然后将每个单元格的概率与黄金数量相乘将所有单元格相加 得到结果 相当于是加权平均值 概率的求法就是1号点的概率为1 之后他能够到达的点(不超过n)的概率相同 为1/x*(前继的概率) x为最多能掷出来的数字
代码如下:
#include<stdio.h> #include<iostream> #include<queue> //#include<algorithm> #include<string.h> using namespace std; queue<int>Q; int t , n , a[110]; double b[110]; void init() { while( !Q.empty() ) Q.pop(); memset(b , 0 , sizeof(b)); } void input() { for(int i=1; i<=n; i++) { scanf("%d" , &a[i]); } } void solve() { b[1] = 1.00; for(int i=1; i<=n; i++) { int j = 1; for( ; j<=6; j++) { if(i+j>n) break; } j--; for(int k=1; k<=j; k++) { b[i+k] += 1.00*b[i]/j; } } double sum = 0.00; for(int i=1; i<=n; i++) { sum += a[i]*b[i]; } printf("%.8lf\n" , sum); } int main() { scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%d" , &n); init(); input(); printf("Case %d: " , cas); if(n == 1) printf("%d\n" , a[1]); else if(n == 2) printf("%d\n" , a[1]+a[n]); else solve(); } return 0
2018-03-09 14:26:3
题意: 小花是个聪明的孩子 给一个数字n 将这个数字除以其中一个因子 得到新的n 一直除下去 直到得到1 问除的次数的期望
题解: 每个数字除的次数可以根据他所有的因子得到 包括1与本身 设d[]表示每个数字除至1的次数的期望(一下简称次数) 那么一个数字的次数等于因子的次数加1 因为需要先除一下到因子 这里的因子指所有因子 假设有c个 那么取到每个因子的概率均为1/c 那么通过这个因子到1的次数的期望为d[因子]/c 假设i1 i2 i3...ic-1 ic 分别代表数字i的c个因子 那么可以得到 d[i] = 1+(d[i1]+d[i2]+...+d[ic-1]+d[ic])/c 化简可得 d[i] = (d[i1]+d[i2]+...+d[ic-1])+c/(c-1) 上面是不包括本身的所有因子的次数的期望 数字为1e5 经过一次预处理 每次输入可以O(1)的输出
代码如下:
#include<stdio.h> #include<iostream> #include<math.h> using namespace std; int t , n; double d[100010]; int num; double sum; double tmp; void init() { d[1] = 0.00; for(int i=2; i<=100000; i++) { num = 0; sum = 0.0; tmp = sqrt(i); num += 2;// 1与本身 // sum += d[1];//d[1]=0 可以省略 for(int j=2; j<tmp; j++) { if(i%j != 0) continue; num += 2; sum += d[j]; sum += d[i/j]; } if(tmp == (int)tmp) { num++; sum += d[(int)tmp]; } // printf("%d...%.8lf\n" , num , sum); d[i] = (sum+num)/(num-1); } } int main() { scanf("%d" , &t); init(); for(int cas=1; cas<=t; cas++) { scanf("%d" , &n); printf("Case %d: %.8lf\n" , cas , d[n]); } return 0; }
D - Just another Robbery LightOJ - 1079
2018-05-06 15:37:51
题意:小花闲的没事要去抢银行 她的队友拼命拦住她说:“蚂蚁竞走了十年了,你冷静一点!” 但是我行故我在 我执意要翘课去抢劫 内心戏怎么这么多 小花的危险值的上限是P 一个分数 有n个银行 每个银行有v[i]元钱 抢劫的时候被抓住的概率是p[i] 问在不被抓住的情况下 最多可以抢劫多少钱
题解:这道题看到之后就知道是一道裸的 0 1 背包的题 每个银行有抢或不抢两种情况 不加空间优化的 01 背包 dp[i][j]在背包中代表装了i件物品 占据了j的体积得到的价值 这道题中dp[i][j]代表的是 抢了i家银行 得到j元钱 被抓到的概率 最后求n个银行都处理完之后 满足概率要求的最大的j 其实这个题刚开始看不会写 因为刚开始会把得到的钱当做价值 概率当做体积 这样的话 真是不知道怎么写 因为概率是小数啊 01 背包没有教我怎么遍历啊 于是转变想法就好 空间压缩的改一下就好
代码如下:
#include<stdio.h> #include<iostream> using namespace std; int t; int n; double P; int v[110]; double p[110]; double dp[105][105*105]; int sum; void init() { sum = 0; } void input() { for(int i=1; i<=n; i++) { scanf("%d%lf" , &v[i] , &p[i]); sum += v[i]; } } void solve() { /* dp[i][j] 记录的是抢了i个银行 得到了j元钱 被抓的概率 */ for(int i=1; i<=sum; i++) dp[0][i] = -1; //抢了0个银行 得到j元钱是不可能的 j>0 dp[0][0] = 0; //抢了0个银行 得到0元钱 被抓的概率是0 for(int i=1; i<=n; i++) { for(int j=0; j<=sum; j++) { /* 因为dp初始值都为0 所以只有在i-1=0的时候才会出现小于-0.5 / <0的情况 */ if(j<v[i] || dp[i-1][j-v[i]]<=-1) //第i个银行强不了(j<v[i]) 或者虽然第i个银行可以抢 但是如果抢了他 他前面的九不成立了 也就是虽然可以建二楼 但是你一楼就垮了 所以二楼还是一楼 dp[i][j] = dp[i-1][j]; else if(dp[i-1][j]<0) dp[i][j] = dp[i-1][j-v[i]] + (1-dp[i-1][j-v[i]])*p[i]; //第i个银行可以抢 因为j>v[i] if里对应抢i-1个银行得不到j元钱的情况 此时第i个银行一定得抢 else dp[i][j] = min(dp[i-1][j], dp[i-1][j-v[i]]+(1-dp[i-1][j-v[i]])*p[i]); //前i-1次被抓的概率是dp[i-1][j-v[i]] 于是不被抓的概率就是1-dp[i-1][j-v[i]] 再乘上此时被抓的概率 } } } int main() { scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%lf%d" , &P , &n); init(); input(); solve(); int ans = 0; // for(int j=0; j<=n; j++) // { for(int i=0; i<=sum; i++) { if(dp[n][i]>-1 && dp[n][i]<P) ans = i; // printf("%lf..." , dp[j][i]); } // printf("\n"); // } printf("Case %d: %d\n" , cas , ans); } return 0; }
E - Birthday Paradox LightOJ - 1104
题意: 给出一年的天数 不一定是365天 你要邀请朋友到你家做客 问最少邀请多少人会使得存在两个人生日相同的概率大于0.5
题解:本小花想开一个二维dp数组 dp[i][0] 表示请i个人 所有人生日都不同的概率 dp[i][1]代表请i个人 至少有两个人生日相同的概率 后来发现 dp[i][1]不就等于1-dp[i][0]么 于是直接求dp[i][0] 一个人的时候为1 因为不可能出现生日相同 之后每加一个人就是乘上(n-i+1)/n
疑问点:惹不起的出题人系列 我考虑了一下 输入为1 和 2的边界情况 发现输出的是1 于是心想 一个人怎么会出现两个人的生日概率相同啊 于是暗自得意 这就是这个题的坑点吧 自信满满的在n==1||n==2的时候特判输出了2 哈哈 不知道为什么WA了 把自以为对的特判删掉就对了 谁来告诉我一下 一个人的时候怎么有两个人的生日相同的概率呢。。 恰逢今天山东省赛 F题暴力1e42能过 让我又想起了去年青岛的区域赛暴力 于是今天真的惹不起出题人了
代码如下:
#include<stdio.h> #include<iostream> using namespace std; int t; int n; double dp[100005]; int main() { // printf("%lf.....%lf...\n" , 1.00*364/(365*365) , (1.00+364*3.0)/(365*365*365)); scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%d" , &n); // if(n <= 2) // { // printf("Case %d: 2\n" , cas); // continue; // } dp[1] = 1; // dp[1][1] = 0; int i; for(i=2; i<=100000; i++) { dp[i] = dp[i-1] * (1.00*(n-i+1)/n); // dp[i][1] = dp[i-1][1] + dp[i-1][0]*(1.00*(i-1)/n); // printf("%d.....%lf........\n" , i , 1-dp[i]); if((1-dp[i]) >= 0.5) break; } printf("Case %d: %d\n" , cas , i-1); } return 0; }
G - Dice (III) LightOJ - 1248
题意: 小花掷硬币 火星硬币有n个面 要求每个面都出现过时的最少投掷次数的期望
思路: dp[i]记录得到i个面投掷次数的期望 我们可以知道dp[1]=1 因为投掷一次只能得到一个面 得到一个面投掷一次就够了
状态转移方程可以参考A题 我要得到第i个面的时候 我有 i-1/n 的概率得到和之前一样的面 有 (n-(i-1))/n的概率在dp[i-1]的基础上得到第 i 个面
于是 dp[i] = dp[i-1]* (n-(i-1))/n +1 + dp[i]* (i-1)/n 为什么要加1呢 因为我们求的是次数的期望 得到一个新的面自然要加1
移项化简得 dp[i] = dp[i-1]+n/(n-i+1)
代码如下:
#include<stdio.h> #include<iostream> using namespace std; int t , n; double dp[100005]; int main() { scanf("%d" , &t); for(int cas=1; cas<=t; cas++) { scanf("%d" , &n); dp[1] = 1.00; for(int i=2; i<=n; i++) { dp[i] = dp[i-1]+1.00*n/(n-(i-1)); } printf("Case %d: %.15f\n" , cas , dp[n]); } return 0; }