概率习题集(from kuangbin)

    xiaoxiao2022-06-30  58

    virtual judge中的kuangbin专题.链接:点击打开链接 .入口二:点击打开链接

    分类浏览:

    简单题:L.E.M

    根据优惠劵收集原理:G.N

    推导一元状态方程:A.B.C.C(1).F.K

    推导多元状态方程:J.Q

    需要特别优化:C(1)

    概率dp:D.I.O

    存在环只能用高斯消元:F.

    剽悍的思路:H.J

    状态压缩:K.O

    P题没能完全理解

    A.

    题意:n个门,每次从n个门中任选一个门,如果该门后面对应的值为负数ai,表示经过时间|ai|,又会回到原点。如果该门后面是正数,则表示经过时间ai后走出迷宫。经过多长时间t能走出迷宫,求该时间的期望。如果不可能走出迷宫输出inf。

    解析:设当前走出该迷宫的期望为X。

    x =∑ 1.0 / n * ai(ai > 0) + ∑1.0 / n * (x + ai) (ai < 0)

    对该式子进行化简,假设正数的个数为p,解得: x =1.0 *( 正数和 + 负数绝对值和 )/ p

    代码:另:需要判断如果都是负数则不可能走出迷宫

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int main() { //freopen("in.txt","r",stdin); int T,a; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n,p = 0; int sum1 = 0,sum2 = 0; scanf("%d",&n); for(int i = 0; i < n; i ++) { scanf("%d",&a); if(a> 0) { sum1 += a; p ++; } else { sum2 += -1 * a; } } int fenzi = sum1 + sum2; int fenmu = p; if(p == 0) { printf("inf\n"); continue; } else { int k = __gcd(fenzi,fenmu); printf("%d/%d\n",fenzi/k,fenmu/k); } } return 0; }

    B.

    题意:你在点1,要走到点n。你选择扔骰子(六个面)的方法,投到多少走几步,但如果投的点数将走的超过点n,则要重新投骰子。每一点都有一个价值,走到该点就能获得,问走到终点时能获得的价值的期望。显然点1,点n 的价值是可以获得的。

    解析:f(x)为从x点出发能获得的价值的期望。边界f(n) = an (边界可以不用)

    f(x) = 1.0 / 6 * f(x + 1) + 1.0 / 6 * f(x + 2)  + 1.0 / 6 * f(x + 3) + ……+1.0 / 6 * f(x + 6) + ax

    当从该点出发可以到达超过点n时,假设可以走的方案有cnt 种

    f(x) = 1.0 / 6 * f(x + 1) + ……+1.0 / 6 * f(x + cnt) + (6 - cnt) / 6.0 * f(x) + ax

    整理后得到:f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt)

    易知:cnt = n - x

    代码: #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int a[105]; double dp[105]; bool vis[105]; int n; double dfs(int x) { //if(x == n) // return a[n]; if(vis[x] == true) return dp[x]; vis[x] = true; dp[x] = a[x]; int num = 6; if(6 + x > n) { num = n - x; } for(int i = 1; i <= num; i ++) { dp[x] += 1.0 / num * dfs(x + i); } return dp[x]; } int main() { //freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); memset(vis,false,sizeof(vis)); scanf("%d",&n); for(int i = 1; i <= n; i ++) { scanf("%d",&a[i]); } double sum = 0; sum = dfs(1); printf("%f\n",sum); } return 0; }

    C(pre 注意是另一道题).题目链接:http://bak2.vjudge.net/problem/20869     uva11762

    题意:我们都知道,任何一个整数都可以写成几个质数的乘积。那么我们将一个整数n,每次从所有小于等于n的素数中任选一个p,如果n能被该素数p整除,则n变为n/p.如果不能,则n不变。问将n变为1,需要选的次数的期望。

    解析:f(x) 表示将x变为1,需要选的次数的期望是多少。其中n表示小于等于x的素数有几个

    f(x) = ∑1.0 / n * f(x / i) (能被整除) + ∑ 1.0 / n * f(x)(不能被整除) + 1

    整理后得到:f(x) = (∑f(x / i) + n) / k * 1.0

    另:需要素数打表

    此题时间复杂度较高时间为10s

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; typedef long long LL; double dp[1000005]; bool vis[1000005]; bool isPrime[2000005]; LL primeList[1000005],primeCount = 0; void primeInit(LL n) { memset(isPrime,true,sizeof(isPrime));//?????????? int m = sqrt(n + 0.5); for(int i = 2; i <= m; i ++) { if(isPrime[i])//????? { for(int j = i * i; j <= n; j += i) { isPrime[j] = false; } } } for(int i = 2; i <= n; i ++) { if(isPrime[i]) { primeList[primeCount] = i; primeCount ++; } } } double dfs(int a) { if(a == 1) return 0.0; if(vis[a] == true) return dp[a]; double ans = 0.0; vis[a] = true; int p = 0,g = 0; while( p < primeCount) { if(primeList[p] > a) { break; } if(a % primeList[p] == 0) { ans += dfs(a / primeList[p]); g ++; } p ++; } if(g == 0) { return 0; } ans = (ans + p) / g; dp[a] = ans; return ans; } int main() { //freopen("in.txt","r",stdin); primeInit(2000000); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { int x; scanf("%d",&x); //减少T大时候的复杂度,对相同n结果是确定的 // memset(vis,false,sizeof(vis)); // memset(dp,0,sizeof(dp)); double ans = dfs(x); printf("Case %d: %f\n",t,ans); } return 0; } C.(1)

    题意:给定一个数N。从N的约数中任选一个i,另N = N / i问要进行这样操作期望的次数,使得N = 1。这里的约数从1到n

    解析:先分析状态方程。设x要到1的期望为f(x)

    f(x) = ∑1.0 / n * f(x/i) (i为x的约数) + 1.0 / n * f(x) + 1

    整理后得:f(x) = 1.0 / (n - 1) * ∑f(x / i) + n / (n - 1) * 1.0

    开始时利用记忆化搜索dp来写,但一直都TLE(3s,题目要求2s)

    实际上该题还需要进一步化简,在求x的约数时可以只求到sqrt(x),如果一个数小于sqrt(x)且是x的约数,那么一定有一个与ta相乘为x,且大于sqrt的约数,如果这两个约数不想等,就把这两个因数都得f都加入到结果中。可以直接打表求。

    另:注意求n/(n - 1)时要转化为double型。

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int const maxn = 100000; double d[maxn + 10]; void init() { int cnt; double sum; for(int i = 2; i <= maxn ; i ++) { cnt = 0; sum = 0; for(int j = 1; j * j <= i; j ++) { if(i % j == 0) { cnt ++; sum += d[j]; if(j != i / j) { cnt ++; sum += d[i / j]; } } } d[i] = 1.0 * (sum + cnt) / (cnt - 1); } } int main() { // freopen("in.txt","r",stdin); int T,a; init(); scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); scanf("%d",&a); printf("%f\n",d[a]); } return 0; } D.

    题意:要求抢劫银行被抓住的概率小于p,银行的个数为n,没家银行都有Mi的钱,在该银行被捉住的概率为pi。求保证被抓住概率小于p时,最多能抢到多少钱。

    解析:显然是dp。dp[x]表示抢劫x,被抓住的最小概率

    dp[x] = dp[x - m[i]] + (1 - dp[x - m[i]]) * pi(在保证dp[x - m[i]] + (1 - dp[x - m[i]]) * pi < p的前提下)

    外层循环从0到n - 1.内层循环钱数从最大值到最小值(保证x - m[i] 的结果是上一层循环的,而不是这一层循环所产生的,其实是二维dp的简化。)

    代码:注意初始化dp均为1(一定会被抓住)dp[0] = 0没抢一定没被抓住

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int a[105]; double p[105]; double dp[10005]; int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n; double P; scanf("%lf%d",&P,&n); int sum = 0; for(int i = 0; i < n; i ++) { scanf("%d%lf",&a[i],&p[i]); sum += a[i]; } for(int i = 0; i < 10005; i ++) { dp[i] = 1; } dp[0] = 0; for(int i = 0; i < n; i ++) { for(int x = sum; x >= 0; x --) { if(x - a[i] >= 0) { if((1 - dp[x - a[i]])* p[i] + dp[x - a[i]] < P) { dp[x] = min(dp[x] ,(1 - dp[x - a[i]]) * p[i] + dp[x - a[i]]); } } } } int ans = 0; for(int i = 0; i <= sum; i ++) { if(dp[i] < P) ans = i; } printf("%d\n",ans); } return 0; } E.

    题意:一年有n天,问至少有多少个人,才能保证有人生日在同一天的概率大于等于0.5

    解析:假设有x个人,那么所有人生日不在一天的概率P为

    分母:n * n * n * n(x个n)

    分子:n * (n - 1) *……*(n - x + 1)

    有分子分母项数的个数是一样的。对应相除即可求解。枚举人数x使得 1 - P >= 0.5。

    优化:每增加一个人实际上只是多成一项 n / (n - x + 1)

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int main() { //freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n; scanf("%d",&n); int ans = 1; double p = 1; while(1) { p = p * (n - ans + 1) / n; if(1.0 - p >= 0.5) { break; } ans ++; } printf("%d\n",ans - 1); } return 0; }

    F.(此题理解不够深刻)

    题意:首先有100个格子,你在第一个格子,要去第100个格子。扔骰子(6面的),投几走几,如果超过100就重新投。另外给出n个点 有两个坐标(a,b)如果你到达了a点就会无条件的传输到b点。(可以从数值大的点传达小点,反之也是)问从1 - 100 扔骰子次数的期望。

    解析:考虑在没有传输的情况下。与b题类似

    设f(x)为从x点走到100点扔骰子的期望

    当x不会超出边界时:

    f(x) = 1.0 / 6 * f(x + 1) + 1.0 / 6 * f(x + 2)  + 1.0 / 6 * f(x + 3) + ……+1.0 / 6 * f(x + 6) + 1

    当从该点出发可以到达超过点n时,假设可以走的方案有cnt 种

    f(x) = 1.0 / 6 * f(x + 1) + ……+1.0 / 6 * f(x + cnt) + (6 - cnt) / 6.0 * f(x) +1

    整理后得到:f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) + 6.0 / cnt

    但是啊因为可以来回传输,就一定会有环。所以只能用高斯消元来求解方程组,那么方程组是什么呢

    对于每个点来说方程为下面的二选一

    1.f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) + 6.0 / cnt

    f(x) - 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) = 6.0 / cnt

    代码中写的是

    cnt * (x) -  f(x + 1)+ ……f(x + cnt) = 6.0

    cnt = n - x.if(cnt > 6)cnt = 6

    2.f(x) = f(b)

    f(x) - f(b) = 0

    一共有100个方程,但由于模板是从1开始的,从0 - equ - 1固传入的数字应为101

    一共有101个变量。高斯 消元中的变元是包括常数的。模板中的变量是从0 - var

    代码:(列方程+ 套模板)

    #include<stdio.h> #include<algorithm> #include<iostream> #include<string.h> #include<math.h> using namespace std; const int MAXN=200; int mapp[MAXN]; const int maxn=1002; const double eps=1e-12; double a[maxn][maxn]; double x[maxn];//解集 bool free_x[maxn]; int n; int sgn(double x) { return (x>eps)-(x<-eps); } // 高斯消元法解方程组(Gauss-Jordan elimination).(0表示无解,1表示唯一解,大于1表示无穷解,并返回自由变元的个数) int Gauss(int equ,int var) { int i,j,k; int max_r; // 当前这列绝对值最大的行. int col; // 当前处理的列. double temp; int free_x_num; int free_index; // 转换为阶梯阵. col=0; // 当前处理的列. memset(free_x,true,sizeof(free_x)); for(k=0; k<equ&&col<var; k++,col++) { max_r=k; for(i=k+1; i<equ; i++) { if(sgn(fabs(a[i][col])-fabs(a[max_r][col]))>0) max_r=i; } if(max_r!=k) { // 与第k行交换. for(j=k; j<var+1; j++) swap(a[k][j],a[max_r][j]); } if(sgn(a[k][col])==0) { // 说明该col列第k行以下全是0了,则处理当前行的下一列. k--; continue; } for(i=k+1; i<equ; i++) { // 枚举要删去的行. if (sgn(a[i][col])!=0) { temp=a[i][col]/a[k][col]; for(j=col; j<var+1; j++) { a[i][j]=a[i][j]-a[k][j]*temp; } } } } for(i=k; i<equ; i++) { if (sgn(a[i][col])!=0) return 0; } if(k<var) { for(i=k-1; i>=0; i--) { free_x_num=0; for(j=0; j<var; j++) { if (sgn(a[i][j])!=0&&free_x[j]) free_x_num++,free_index=j; } if(free_x_num>1) continue; temp=a[i][var]; for(j=0; j<var; j++) { if(sgn(a[i][j])!=0&&j!=free_index) temp-=a[i][j]*x[j]; } x[free_index]=temp/a[i][free_index]; free_x[free_index]=0; } return var-k; } for (i=var-1; i>=0; i--) { temp=a[i][var]; for(j=i+1; j<var; j++) { if(sgn(a[i][j])!=0) temp-=a[i][j]*x[j]; } x[i]=temp/a[i][i]; } return 1; } int main(void) { // freopen("in.txt", "r", stdin); //freopen("out.txt","w",stdout); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { memset(mapp,0,sizeof(mapp)); memset(a,0,sizeof(a)); printf("Case %d: ",t); int n; scanf("%d",&n); int p,q; for(int i = 0; i < n; i ++) { scanf("%d%d",&p,&q); mapp[p] = q; } a[100][100] = 1,a[100][101] = 0; for(int i = 1; i < 100; i ++) { if(mapp[i] != 0) { a[i][i] = 1; a[i][mapp[i]] = -1; a[i][101] = 0; } else { int k = 0; for(int j = 1; j <= 6 && i + j <= 100; j ++) { a[i][i + j] = -1; k ++; } a[i][i] = k; a[i][101] = 6; } } Gauss(101,101); printf("%.16f\n",x[1]); } return 0; } G.

    题意:假设一个骰子有n面,设扔x次能使得每面至少被扔到一次,求x的期望。

    解析:可以直接观察找规律。但注意此题是有一类经典题目优惠劵收集问题:链接http://www.guokr.com/article/5583/

    第一次扔骰子,想要扔到一个新的点数需要扔一次,第二次扔骰子,扔到新的点数的概率为 (n - 1)/ n.那么需要扔骰子的期望数为n/ (n - 1) ,再扔骰子,需要扔骰子的期望数为n/(n - 2) ……直到最后一次扔骰子,扔到新的点数的概率为1 / n,需要扔 n / 1次

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n; scanf("%d",&n); double ans = 1.0; for(int i = 1; i < n; i ++) { ans += 1.0 * n / i; } printf("%.8f\n",ans); } return 0; } H.

    题意:你在一个岛上,岛上有老虎,鹿,还有你。另外有一下几条规则:1.如果你遇到老虎,老虎就会吃掉你。2.如果鹿遇到老虎,鹿会被老虎吃掉。3.如果鹿遇到鹿,相安无事4.如果你遇到鹿,你会杀死鹿,或者留下鹿。5.如果老虎遇到老虎,它们会同归于尽。每天两个生物将会相遇,如果老虎都死了,你将获救。如果遇到鹿,你要做出最佳的选择,求你存货的概率是多少

    解析:此题的破题点在于与鹿的个数是没有关系的。因为鹿的出现只会增加轮数,对老虎是否会同归于尽没有任何影响。

    如果老虎的个数是单数,人必死。如果老虎的个数为0,人比活。

    每次从所有的老虎和人中选择两个,再从老虎中选择两个,直到没有老虎了

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; double dp[1005][1005]; int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { memset(dp,0,sizeof(dp)); printf("Case %d: ",t); int x,y; double ans = 1; scanf("%d%d",&x,&y); if(x == 0) { ans = 1; } else if(x % 2 != 0) { ans = 0; } else { // cout<<1<<endl; while(x) { ans = ans*1.0 * (x - 1) * x / x / (x + 1); x = x - 2; } } printf("%.8f\n",ans); } return 0; } I

    题意:题面很长。意思就是有n个yes / no,它们的总长度为s。根据这个能算出有多少个yes,多少个no。对于有确定个数的yes、no的每一种排列。比如

    no yes no yes.每一个yes/no向后移一位,前面补yes

    yes no yes no 这样对于这种排列就有四个是错的,求对于所有排列错误数的期望

    解析:dp。dp[i][j][k],i表示前i个位置,j表示前i个位置有几个yes,k表示当前位置是yes还是no。0表示yes,1表示no.整体表示对于这种情况错误的期望数。

    p表示的是第i个位置之后的位置是yes的可能性。p = (yes个数 - j) / (n - i)

    q为no的可能性q = 1.0 - p

    dp[i][j][0] = dp[i + 1][j + 1][0] * p + (dp[i + 1][j][1] + 1)* q;

    dp[i][j][1] =  (dp[i + 1][j + 1][0] + 1) * p + dp[i + 1][j][1] * q

    最后的值为dp[1][1][0] + dp[1][0][1]分别表示第一个位置是yes,和no

    由于存不下,第一位采用的是滚动数组。dp的第一维应当是从大到小的,第二 维要注意保证yes的个数是合法的,既不能少于i - no的个数,也不能大于i,yes的个数

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; double dp[2][5005][2];//yes = 0,no = 1; int main() { //freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n,s; scanf("%d%d",&n,&s); int x = s - 2*n,y = 3 * n - s ;//x表示yes的个数,y表示no的个数 memset(dp,0,sizeof(dp)); int cur = 1;//滚动数组,表示i == 1 时求出的值是存在dp[0]中单 if(n % 2 == 0) cur = 0; //dp[cur][x][0] = dp[cur][x][1] = 0; for(int i = n - 1; i >= 1; i --) { // cout<<cur<<endl; cur = cur ^ 1; for(int j = max(i - y, 0 ); j <= i&& j <= x; j++) { double p1 = 1.0 * (x - j) *1.0/ (n - i); double p2 = 1.0 - p1; dp[cur][j][0] = dp[cur ^ 1][j + 1][0] * p1 +( dp[cur ^ 1][j][1] + 1)* p2; dp[cur][j][1] = (dp[cur ^ 1][j + 1][0] + 1) * p1 + dp[cur ^ 1][j][1] * p2; } } printf("%f\n",dp[1][1][0] * x / n +( dp[1][0][1] + 1)* y / n); } return 0; } J.

    题意:从长宽高分别为xyz中,先任选三个x1,y1,z1,再任选三个x2,y2,z2将min(x1, x2) ≤ x ≤ max(x1, x2),min(y1, y2) ≤ y ≤ max(y1, y2) andmin(z1, z2) ≤ z ≤ max(z1, z2).的灯全部打开。开始为全部关上。重复k轮。问开着灯的个数的期望。

    解析:先求解出每个点的等开着的概率。设该点为a,b,c.选择的x为x1,x2.该点开灯的概率为:1.0 - x1,x2都选在该点左侧的概率(a - 1) * (a - 1) / (x * x) - 都选在该点右侧的概率(x - a) * (x - a) / (x * x) 。设每个点一次后被按到的概率为p。那么k轮后开着灯的概率为 被按到的概率为奇数的概率。设按k次被按到的次数为奇数的概率为f(k) .被按到次数为偶数的概率为 g(k)。f(k) = f(k - 1) * ( 1 - p) + g(k - 1) * p.g(k) = g(k - 1) * (1 - p) + f(k -1) * p.f(1) = p,g(1) = 1 - p.f(k) + g(k) = 1

    整理后可以得到:f(k) = f(k - 1) * ( 1 - 2*p) + p

    另f(k) + C = (1 - 2p) * (f(k - 1) + C) (解出C= -1.0 / 2.0)

    就得到了:f(k) = ( 1 - ( 1 - 2p) ^k) / 2.0;

    将每个点的概率求出来,再乘以总共灯的个数。

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; double fun(int x,int y) { double ans = 1.0 -1.0 *( (x - 1) * (x - 1) + (y - x) * (y - x) )/ (y * y); return ans; } int main() { //freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int a,b,c,n; double p = 0,ans = 0; scanf("%d%d%d%d",&a,&b,&c,&n); if(n == 0) { printf("0\n"); continue; } for(int i = 1; i <= a; i ++) { for(int j = 1; j <= b; j ++) { for(int k = 1; k <= c; k ++) { p = fun(i,a) * fun(j,b) * fun(k,c) ; ans += -0.5 * pow(1 - 2*p,1.0 * n) + 0.5; } } } printf("%.8f\n",ans); } return 0; } K.(没有理解好)

    题意:T组数据,n个顶点,m个边。之后是m条边,分别是两个顶点,和权值及跑过路径的时间(u,v,w).坏人一开始在接点零处,坏人经过一个顶点后就不能再次回到那个顶点了。坏人可以走到有边和它相连,并且从该点出发可以经过所有未经过的点。假设这样的方案有p种。坏人还可以就在该点再呆5min。这样坏人选择每一种方案的概率为1/(1+p)如果没有顶点可以走的时候,坏人被警察抓住。求坏人被警察抓住所需时间的期望

    解析:状态压缩 + dp。将每个点是否走过,用二进制压缩成一串零一序列。之后用dfs记忆化搜索,判断条件为状态是否为每个点都走过,如果都做过则返回true

    状态方程为设从当前点和状态出发一共可以走的点数为cnt。从当前点和状态出发被警察抓住时间的期望为f(x)

    f(x) = 1.0 / (cnt + 1) * (f(x) + 5.0) + ∑1.0 / (cnt + 1) * (wi + f(y))(从当前点可以到达的状态和位置)

    整理后:f(x) = (∑ (wi + f(y)) + 5 ) / cnt

    为什么是状态压缩呢?因为当前点是否能够遍历所有安全的点,与哪些点是已经遍历过的有关。也就是说要已知过去的状态才能推导当前的状态。即必须要存储当前状态

    代码:

    /*int t,n,m; double mp[maxn][maxn]; double dp[maxn][1<<maxn]; bool vis[maxn][1<<maxn]; //化简后的公式为dp[u] = 5 / cnt + (求和 map[u][i] + dp[i]) / cnt int dfs(int st,int u){//u是当前点.如st=0110,那么这个状态是去过1,2两个点的 if (st==(1<<n)-1){//如果已经去过了所有的点,表示从某点出发能到达所有的点 dp[u][st]=0; return 1; } if (vis[u][st]) return dp[u][st]>eps; int cnt=0; dp[u][st]=5; vis[u][st]=true; for(int i=0;i<n;i++) if (mp[u][i]&&dfs(st|(1<<i),i)&&(!(st&(1<<i)))){//最后一个条件判断当前点是否去过。 int st0=st|(1<<i); cnt++; dp[u][st]+=mp[u][i]+dp[i][st0]; } if (cnt){ dp[u][st]/=cnt; return 1; } dp[u][st]=0;//从某点出发不能到达所有的点 return 0; } int main(){ //input; scanf("%d",&t); for(int Case=1;Case<=t;Case++){ memset(dp,0,sizeof(dp)); memset(mp,0,sizeof(mp)); memset(vis,false,sizeof(vis)); scanf("%d%d",&n,&m); int u,v,w; while(m--){ scanf("%d%d%d",&u,&v,&w); mp[u][v]=mp[v][u]=1.0*w; } dfs(1,0); printf("Case %d: %.10lf\n",Case,dp[0][1]); } return 0; } */ #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; double mapp[20][20]; double dp[20][1 << 16]; bool vis[20][1 << 16]; int n; double dfs(int u,int st) { if(st == (1 << n) - 1) { dp[u][st] = 0; return 1; } if(vis[u][st] == true) return dp[u][st]; dp[u][st] = 5.0; vis[u][st] = true; int cnt = 0; for(int i = 0; i < n; i ++) { if(mapp[u][i] != 0 && ( !(st & (1 << i))) && dfs(i,st | (1<<i)))//保证当前点之前是没有走过的,增加当前点dfs的结果不是零即可 { cnt ++; dp[u][st] += mapp[u][i] + dp[i][st | (1<<i)]; } } if(cnt != 0) { dp[u][st] /= cnt; return 1.0; } dp[u][st] = 0; return 0; } int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); memset(dp,0,sizeof(dp)); memset(mapp,0,sizeof(mapp)); memset(vis,false,sizeof(vis)); int m,u,v,w; scanf("%d%d",&n,&m); for(int i = 0; i < m; i ++) { scanf("%d%d%d",&u,&v,&w); mapp[u][v] = w * 1.0; mapp[v][u] = w * 1.0; } dfs(0,1); printf("%f\n",dp[0][1]); } return 0; } L.

    题意:n个人扔球任意投入到m个框中,投k次。每个人每次将球投入到任意一个框中的概率为p(也就是说要对准一个框投入的概率为p)问投完后所有框中球的个数之和的期望。

    解析:水题。一个人投一次投入到一个框中的球的期望为p,投入到m个框中依旧为p(框是任意的,投完加和) 投k次期望为kp,n个人期望为nkp。简单相乘就行

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n,m,k; double p; scanf("%d%d%d%lf",&n,&m,&k,&p); printf("%.8f\n",n * k * p); } return 0; } M

    题意:T组数据。n个点,m条边,S大小的数据,没一个大小的数据来回需要2*k的时间。m条边,每条边都有一个pi表示发送成功的概率。是双向边。如果发送失败则需要从新发送。每次发送每一个大小的无论成功与否都要耗费2 * k的时间。求发送数据从0点到n - 1最小的期望时间。

    解析:因为要求的是最小的期望时间,首先要求出从0 到n - 1,发送成功概率最大的路。由于n较小利用floyd即可。发送一次成功的概率为p。那么对于一个数据大小的发送成功的期望时间为2 * k/ p,对于s大小的数据来说则是 2 * k * s / p

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; //pay attention to use long long double d[105][105]; int n; void Floyd() { for(int k = 0; k < n; k ++) { for(int i = 0; i <n; i ++) { for(int j = 0; j < n; j ++) { d[i][j] = max(d[i][j],d[i][k] * d[k][j]); } } } } int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); memset(d,0,sizeof(d)); int m,k,u,v,p; long long s; scanf("%d%d%I64d%d",&n,&m,&s,&k); for(int i = 0; i < m; i ++) { scanf("%d%d%d",&u,&v,&p); d[u][v] = p * 0.01; d[v][u] = p * 0.01; } Floyd(); printf("%f\n",s * k * 2.0 / d[0][n - 1] ); } return 0; } N.(不理解)

    题意:n个棍子,一种棍子可以和其它的棍子区别开来,一种不能。每根棍子有一个重量。一个人每次选择一根棍子,为了使得每根棍子都至少被选择过一次,求棍子重量的期望。

    解析:又见优惠劵收集问题。如果只有不能和其他棍子区别开来的棍子,那么棍子的期望是可以算出来的。

    但此题是按照贡献的期望数来计算的,能区分开的棍子贡献的期望数为棍子的重量本身。

    按照全部都是不能区分开,需要抽n * Hn次(Hn为调和级数),这些次数抽到的棍子重量的期望为n * Hn * (总重量)/ n,那么对于每根不可区分的棍子来说,对期望的贡献时间为Hn * 棍子的重量。而对于可以区分的期望只能为棍子的重量

    思路实际上是认为所有的棍子都是抽到后放回的(不能区分开的),来求期望数。抽到之后,还能再抽。但在能区分开的棍子的期望时则只计算一次

    代码:

    #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; double H[5005]; int main() { // freopen("in.txt","r",stdin); for(int i = 1; i <= 5005; i ++) { H[i] = H[i - 1] + 1.0 / i; } int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n,a,b; scanf("%d",&n); double ans= 0; for(int i = 0; i < n; i ++) { scanf("%d%d",&a,&b); if(b == 1) { ans += a; } else { ans += H[n] * a; } } printf("%.6f\n",ans); } return 0; } O.

    题意:一套扑克54张牌,其中大小王为混子。问使得你手上每一种花色都大于等于给定数目,需要发多少张牌的期望。如果不可能,则输出-1 解析:概率dp + 状态压缩 + 记忆化搜索

    dp[a][b][c][d][5][5] // 前4项分别表示未发给你的各种花色的数量。第五,六维 = 0 表示你没有大小王,1,2,3,4表示大小王被认为是这四种不同的花色

    表示从当前状态开始达到要求状态还需要发牌的数量

    dp[a][b][c][d][x1][x2] += dp[a - 1][b][c][d][x1][x2] * a / tot (另:b,c,d)

    tot 为牌的总数,注意考虑是否有大小王

    另外如果大小王还没有用,就要遍历让大小王等于四种花色中的期望数最小的一个

    dp[a][b][c][d][x1][x2] += dp[a][b][c][d][i][x2]  / tot (大小王只有一张牌所以 * 1.0 省略)

    dp[a][b][c][d][x1][x2] += 1.0;//总排数加1

    边界条件当你手上的各种牌已经满足条件了,返回零

    将每个要求的牌数大于13的部分加起来如果大于2,则是违法的输出-1

    代码:

    #include <iostream> #include <cstdio> #include <cstring> using namespace std; double dp[20][20][20][20][5][5];//注意是double 类型的 int q[10]; int A,B,C,D; double dfs(int a,int b,int c,int d,int x1,int x2) { double ans= dp[a][b][c][d][x1][x2]; if(ans > 0) return ans; q[1] =13 - a,q[2] =13 - b,q[3] = 13 - c,q[4] = 13 - d; q[x1] ++,q[x2] ++; if(q[1] >= A && q[2] >= B && q[3] >= C &&q[4] >=D) return 0; int tot = a + b + c + d + (x1 == 0? 1:0) + (x2 == 0? 1 :0);//如果 x== 0表示王海没用过,总排数加1 if(a > 0) ans += 1.0 * a / tot * dfs(a - 1,b,c,d,x1,x2); if(b > 0)ans += 1.0 * b / tot * dfs(a,b - 1,c,d,x1,x2); if(c > 0)ans += 1.0 * c / tot * dfs(a,b,c - 1,d,x1,x2); if(d > 0) ans += 1.0 * d / tot * dfs(a,b,c,d - 1,x1,x2); if(x1== 0)//如果大小王还没用,就有概率抽到大小王,收到大王的概率为 1.0 /tot { double t = 100000; for(int i = 1; i <= 4; i ++) { t = min(t,dfs(a,b,c,d,i,x2)); } ans += 1.0 * t / tot; } if(x2== 0) { double t = 100000; for(int i = 1; i <= 4; i ++) { t = min(t,dfs(a,b,c,d,x1,i)); } ans += 1.0 * t / tot; } ans += 1.0; dp[a][b][c][d][x1][x2] = ans; return ans; } int main() { // freopen("in.txt","r",stdin); int T; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); scanf("%d%d%d%d",&A,&B,&C,&D); int cnt= 0;//注意计算的是超过13的总个数,没超过的不要加起来 if(A > 13) cnt+= A - 13; if(B > 13) cnt+= B - 13; if(C > 13) cnt+= C - 13; if(D> 13) cnt+= D - 13; if(cnt > 2) { printf("-1\n"); continue; } memset(dp,0,sizeof(dp)); printf("%.8f\n",dfs(13,13,13,13,0,0)); } return 0; } P.(完全且依旧不理解)(网上没题解啦(;′⌒`))

    题意:再见迷宫(很眼熟啦)区别就是你会记住你选择的过去的k 个门,而不会再打开,问走出迷宫的期望。

    解析:题面的类似,和做法的不同。。。

    首先还是要判断是否能走出去。然后dfs(cnt1,cnt2,t) 三个参数分别是有cnt1个正数,cnt2个负数,和已经走了t步

    一般情况下 return cnt1 / (cnt1 + cnt2) * av1 + (dfs(cnt1,cnt2 - 1,t + 1) + av2) * cnt2 / (cnt1 + cnt2)

    如果cnt2<= 0 return av1

    如果t >= k即已经记住了k个门了,不会再多了。那么此种情况和A题是一样的 直接就能求出结果return 1.0 *(c1 * av1 + c2 * av2 )/ c1;

    代码:

    //注意两个整数相处得到一个double型要乘1.0 #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int k; int sum1 ,sum2 ; int cnt1,cnt2; double av1,av2 = 0; double dfs(int c1,int c2,int t) { if(c2 <= 0) return av1; if(t >= k) { return 1.0 *(c1 * av1 + c2 * av2 )/ c1; } return 1.0 * c1 /(c1 + c2) * av1 + 1.0 * c2/(c1 + c2) *(dfs(c1,c2 - 1,t + 1) + av2); } int main() { //freopen("in.txt","r",stdin); int T,a; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n; sum1 = 0,sum2 = 0,cnt1= 0,cnt2 = 0,av1 = 0,av2 = 0; scanf("%d%d",&n,&k); for(int i = 0; i < n; i ++) { scanf("%d",&a); if(a> 0) { sum1 += a; cnt1 ++; } else { sum2 += -1 * a; cnt2 ++; } } if(cnt1) av1 =1.0* sum1 / cnt1; if(cnt2) av2 =1.0 *sum2 / cnt2; if(cnt2 == n) { printf("-1\n"); } else { printf("%.8f\n",dfs(cnt1,cnt2,0)); } } return 0; } Q.(最初表示不够理解)中间推导公式不够理解

    参照的题解:http://blog.csdn.net/yeyeyeguoguo/article/details/46446905,

    http://blog.csdn.net/whyorwhnt/article/details/9904151

    题意:投中求的概率为p.如果连续投不中球k1次,或连续投中球k2次练习结束,求投球数的期望

    解析:f(i)表示连续投中i次后剩余投球个数的期望数,g(i)表示连续投不中i次的期望数,剩余投球个数的期望数

    注意:求期望数别忘记加1

    f(i) = f(i + 1) * p  + g(1) * (1 - p)  + 1

    g(i) = g(i + 1) * ( 1 -p) + f(1) * p + 1

    已知边界f(k2) = 0,g(k1) = 0

    可以求出f(1),g(1):推导过程

    设x = g(1) * (1-p) + 1.不要按通解来求

    f(i) = f(i + 1) * p + x.

    f(k2) = 0,f(k2 -1) = x,f(k2 - 2) = p*x + x,f(k2 -3) = p * p * x + p *x + x

    顾f(1) = p^k - 2 * x +  p ^ k - 3 * x +……+p ^ 0*x

    f(1) =  (1 - p^(k - 1)) / (1 - p) * (g(1) * (1-p) + 1)

    求出g(1) 。两方程联立求解

    f(0) = f(1) * p + g(1) * (1-p) + 1,g(0) = ……

    最后答案为f(0) + g(0)

    代码:

    //注意两个整数相处得到一个double型要乘1.0 #include <iostream> #include <cstdio> #include <cstring> #include <sstream> #include <string> #include <algorithm> #include <list> #include <map> #include <vector> #include <queue> #include <stack> #include <cmath> #include <cstdlib> using namespace std; int k; int sum1 ,sum2 ; int cnt1,cnt2; double av1,av2 = 0; double dfs(int c1,int c2,int t) { if(c2 <= 0) return av1; if(t >= k) { return 1.0 *(c1 * av1 + c2 * av2 )/ c1; } return 1.0 * c1 /(c1 + c2) * av1 + 1.0 * c2/(c1 + c2) *(dfs(c1,c2 - 1,t + 1) + av2); } int main() { //freopen("in.txt","r",stdin); int T,a; scanf("%d",&T); for(int t = 1; t <= T; t ++) { printf("Case %d: ",t); int n; sum1 = 0,sum2 = 0,cnt1= 0,cnt2 = 0,av1 = 0,av2 = 0; scanf("%d%d",&n,&k); for(int i = 0; i < n; i ++) { scanf("%d",&a); if(a> 0) { sum1 += a; cnt1 ++; } else { sum2 += -1 * a; cnt2 ++; } } if(cnt1) av1 =1.0* sum1 / cnt1; if(cnt2) av2 =1.0 *sum2 / cnt2; if(cnt2 == n) { printf("-1\n"); } else { printf("%.8f\n",dfs(cnt1,cnt2,0)); } } return 0; }

    转载请注明原文地址: https://ju.6miu.com/read-1125654.html

    最新回复(0)