概率和期望dp
概率和期望好神啊,完全不会.
网上说概率要顺着推,期望要逆着推,然而我目前做的概率期望题正好都与此相反2333
概率:
关于概率:他非常健康
初中概率题非常恐怖。现在来思考一道题:中国放弃参加IOI2018的概率是多少?理性的回答:趋近于0;asuldb的回答:和他NOIP AK的概率差不多;按照初中的观点:1/2(有可能放弃,有可能不放弃),所以他有挺大的可能AK NOIP啦.
有一次期中考试做过一道题:小明的班里有3/4的人学数学,1/4人学英语,问小明学数学的概率是多少。答案竟然还是1/2(有可能学数学,有可能学英语)...简直无言以对。
不过现在这样就不行了,比如说下面这个题。
卡牌游戏:https://www.luogu.org/problemnew/show/P2059
题意概述:n个人排成一圈,从第一个人开始从m张牌中抽出一张,并将从他开始顺时针数牌上数字的人淘汰,这个人左边的人接着开始抽牌,最后剩下的人胜出。求每个人赢的概率。$n,m<=50$
那不就是1/n吗。看起来挺适合搜索的,但是搜索被卡到0分。这道题虽然是概率,但是是逆着推的,思路非常巧妙:用$dp[i][j]$表示还剩下i个人时,从抽牌人顺时针数j个数的那个人赢的概率。
因为只剩一个人的时候是肯定可以赢的,所以考虑从小往大转移。比如说dp[i][j]等于几呢,枚举这一次抽的牌,算出来淘汰的人为顺时针数第k个人。
然后可以发现,如果$k>j$,那么$j$在新的一轮的编号就是$j-k$,如果$k<j$,那么$j$在新的一轮的编号就成了$i-k+j$.如果等于呢?$j$都被淘汰了...
最后的答案就是顺序输出$dp[n][i]$。
1 # include <cstdio> 2 # include <iostream> 3 # define R register int 4 5 using namespace std; 6 7 int n,m,a[100],l; 8 double dp[51][51]; 9 10 int main() 11 { 12 scanf("%d%d",&n,&m); 13 for (R i=1;i<=m;++i) 14 scanf("%d",&a[i]); 15 dp[1][1]=1.0; 16 for (R i=2;i<=n;++i) 17 for (R j=1;j<=i;++j) 18 for (R k=1;k<=m;++k) 19 { 20 l=a[k]%i; 21 if(!l) l=i; 22 if(l==j) continue; 23 if(l>j) 24 dp[i][j]+=dp[i-1][i-l+j]/m; 25 else 26 dp[i][j]+=dp[i-1][j-l]/m; 27 } 28 for (R i=1;i<n;++i) 29 printf("%.2lf%% ",dp[n][i]*100); 30 printf("%.2lf%%",dp[n][n]*100); 31 }
概率充电器:https://www.lydsy.com/JudgeOnline/problem.php?id=3566
题意概述:n-1 条导线连通了 n 个充电元件。进行充电时,每条导线是否可以导电以概率决定,每一个充电元件自身是否直接进行充电也由概率决定。随后电能可以从直接充电的元件经过通电的导线使得其他充电元件进行间接充电,问最后进入充电状态的元件个数的期望是多少。$n<=500000$
看起来像个期望,但是每个元件都是"1"个,所以也就是概率相加,按照概率来做。显然是树形dp。一开始每个点被充电的概率是$q_i$,然后先从儿子到父亲更新,注意概率不是加!要有全概率公式的那种“概率加法”,建议定义一个函数用于实现概率加法,否则写着写着又串了。然后从父亲到儿子更新,但是注意不能重复更新,要容斥一下,把儿子向父亲传的概率减掉,但这里也不是普通的减法,是概率减法!也就是说:父亲给儿子充电的概率是父亲充上电的概率减去儿子给父亲充电的概率。对于两个相互独立的事件A、B,P(A+B)=P(A)+P(B)-P(A)*P(B),P(A)=(P(A+B)-P(B))/(1-P(B))。只要把概率的运算弄明白,这就是一个简单的up and down的题目了。交了好多次才过qwq
这道题还有一种逆向做法,考虑每个元件充不上电的概率是多少,这时候就比较简单了。
1 # include <cstdio> 2 # include <iostream> 3 4 using namespace std; 5 6 const int maxn=500005; 7 int n,a,h,b,firs[maxn],dep[maxn]; 8 double p,q[maxn],ans,f[maxn]; 9 struct edge 10 { 11 int nex,too; 12 double p; 13 }g[maxn<<1]; 14 15 void add (int a,int b,double p) 16 { 17 g[++h].too=b; 18 g[h].nex=firs[a]; 19 firs[a]=h; 20 g[h].p=p; 21 } 22 23 double add (double a,double b) 24 { 25 return a+b-a*b; 26 } 27 28 void dfs1 (int x) 29 { 30 int j; 31 for (int i=firs[x];i;i=g[i].nex) 32 { 33 j=g[i].too; 34 if(dep[j]) continue; 35 dep[j]=dep[x]+1; 36 dfs1(j); 37 q[x]=add(q[x],g[i].p*q[j]); 38 } 39 } 40 41 double ab (double a) 42 { 43 if(a<0) return -a; 44 return a; 45 } 46 47 bool zero (double x) 48 { 49 if(ab(x)<=0.0000001) return true; 50 return false; 51 } 52 53 void dfs2 (int x) 54 { 55 ans+=q[x]; 56 int j; 57 double t; 58 for (int i=firs[x];i;i=g[i].nex) 59 { 60 j=g[i].too; 61 if(dep[j]<dep[x]) continue; 62 if(zero(1.0-g[i].p*q[j])) q[j]=1.0; 63 else 64 { 65 t=(q[x]-g[i].p*q[j])/(1.0-g[i].p*q[j]); 66 q[j]=add(q[j],t*g[i].p); 67 } 68 dfs2(j); 69 } 70 } 71 72 int main() 73 { 74 scanf("%d",&n); 75 for (int i=1;i<n;++i) 76 { 77 scanf("%d%d%lf",&a,&b,&p); 78 p*=0.01; 79 add(a,b,p); 80 add(b,a,p); 81 } 82 for (int i=1;i<=n;++i) 83 scanf("%lf",&q[i]),q[i]*=0.01; 84 dep[1]=1; 85 dfs1(1); 86 dfs2(1); 87 printf("%.6lf",ans); 88 return 0; 89 }
概率太有意思了,不过一定要记住把理论应用到实际中去。比如说我上午看了两个小时的条件概率、全概率公式和贝叶斯公式,下午做题的时候竟然还把概率相加...
设$P(A,B)$为$A,B$两事件至少有一个发生的概率,则$P(A,B)=P(A)+P(B)-P(A)*P(B)$
贝叶斯公式:$P(A|B)$表示在我们观测到B事件已经发生时A发生的概率。分母那里就是一个全概率公式。
条件概率: 因为这里的$XY$不再是互不相关的事件了。
条件概率:后验概率=先验概率*调整因子
看起来非常枯燥,让我编一个故事来休闲一下,当然也可以增进一下认识:
今天是星期四,shzr就开始想着放假了...但是这也是有一定道理的,毕竟一中每次都是周四通知放假的消息,关键是,周五很有可能并不放假!所以周四就是很关键的一天了。但是shzr已经等不及了,他不打算等到晚上五点,而是很早就开始各种打探消息。不对,他决定先推理一下。每两周都要放假,所以放假的概率是1,但是下一周就是(清明/五一/端午)了,这种时候不放假也是有可能的,就认为放假的概率是50%。所以还是要打探消息的。数学组的同学说要放假,但是数学组的情报常常是不可靠的,毕竟他们每天都说有突发情况要放假,有0.8的概率这个情况并不可靠,所以你打算用一下概率的公式,但是这里似乎还有一点问题,不可靠的情报意味着纯粹是编出来的,编出来的也不一定就是错的,所以我们认为如果他们找到的是不可靠情报,依旧有50%的可能性是正确的,所以他们的正确率就是0.6:
如果是正确的:他说放假, 真的放假的概率是:60%*50%=30%
如果是错的:他说放假,却没有放假的概率是:40%*50%=20%
那么放假的概率就是60%,不放假的概率就是40%.但是看起来60%并不是很让人满意...后来又有了新的信息:生物组通知要放假。这个消息的可信度是有的,但是生物组和信息组并不一定同时放假,只是他们放假的事也是有过的,这种情况发生的概率据推断是20%,这是说,他们是否放假和我们并没有任何关系的概率是20%,不代表在这些情况中我们就一定不能放假。
如果这是一个无关的信息:放假概率:20%*60%=12%; 不放假概率:20%*40=8%;
如果这是一个统一的假期:80%*100%=80%;
所以放假的概率就是92%,这就非常好了。下午五点通知:放假;看来这个概率还是比较准确的~
期望:
什么是期望:以概率为权,对所有可能结果做的加权平均数。
概率有很多性质...如果不知道大概就没法做题了,每学一点就在这里补一点吧:
换教室:https://www.luogu.org/problemnew/show/P1850
题意概述:题意非常复杂...
知道了刚刚那些公式之后就发现是一个水题了...我一开始做的时候是用$dp[i][j][k][z]$表示前i个教室,提交了j个申请,k表示这一个在哪里,然而这样是不行的,或者说非常难做的,而且挺麻烦的。另一种比较科学的定义方法是只看这一次是否申请,不看结果,非常好做!状态转移有点麻烦,但是总是可以码完的(写过树套树,华容道的人表示无所畏惧了),关键是一定要记住一句话,和的期望等于期望的和!算概率的时候别把上一个的期望长度也给乘进去了,那就错了...加上就好。如果您对期望的认识比较浅,可以看一下这里:取min只能是在决策中取,绝对不能对于每种情况取,因为决策确定后期望也就唯一确定了,不能分开来看了。下面是被金牌爷骂非常丑陋的代码:
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # define R register int 6 7 using namespace std; 8 9 const int maxn=2005; 10 const int inf=9000009; 11 int n,m,v,e,a,b,co; 12 int c[maxn],d[maxn]; 13 double k[maxn]; 14 int F[305][305]; 15 double dp[maxn][maxn][2]; 16 17 int main() 18 { 19 scanf("%d%d%d%d",&n,&m,&v,&e); 20 for (R i=1;i<=n;++i) 21 scanf("%d",&c[i]); 22 for (R i=1;i<=n;++i) 23 scanf("%d",&d[i]); 24 for (R i=1;i<=n;++i) 25 scanf("%lf",&k[i]); 26 for (R i=1;i<=v;++i) 27 for (R j=1;j<=v;j++) 28 F[i][j]=inf; 29 for (R i=1;i<=v;++i) 30 F[i][i]=0; 31 for (R i=1;i<=e;++i) 32 { 33 scanf("%d%d%d",&a,&b,&co); 34 F[a][b]=min(F[a][b],co); 35 F[b][a]=F[a][b]; 36 } 37 for (R z=1;z<=v;++z) 38 for (R i=1;i<=v;++i) 39 for (R j=1;j<i;++j) 40 F[j][i]=F[i][j]=min(F[i][j],F[i][z]+F[z][j]); 41 memset(dp,0x7f,sizeof(dp)); 42 dp[1][0][0]=0; 43 dp[1][1][1]=0; 44 for (R i=2;i<=n;++i) 45 { 46 dp[i%2][0][0]=dp[(i-1)%2][0][0]+F[ c[i] ][ c[i-1] ]; 47 for (R j=1;j<=m;++j) 48 { 49 dp[i%2][j][0]=min(dp[(i-1)%2][j][0]+F[ c[i] ][ c[i-1] ] , dp[(i-1)%2][j][1]+(1-k[i-1])*F[ c[i] ][ c[i-1] ]+k[i-1]*F[ c[i] ][ d[i-1] ]); 50 dp[i%2][j][1]=min(dp[(i-1)%2][j-1][0]+(1-k[i])*F[ c[i] ][ c[i-1] ]+k[i]*F[ d[i] ][ c[i-1] ] , dp[(i-1)%2][j-1][1]+k[i]*k[i-1]*F[ d[i] ][ d[i-1] ]+k[i]*(1-k[i-1])*F[ d[i] ][ c[i-1] ]+(1-k[i])*k[i-1]*F[ c[i] ][ d[i-1] ]+(1-k[i])*(1-k[i-1])*F[ c[i] ][ c[i-1] ]); 51 } 52 } 53 54 double ans=dp[n%2][0][0]; 55 for (R i=1;i<=m;i++) 56 { 57 ans=min(ans,dp[n%2][i][0]); 58 ans=min(ans,dp[n%2][i][1]); 59 } 60 printf("%.2lf",ans); 61 return 0; 62 }
OSU!:https://www.lydsy.com/JudgeOnline/problem.php?id=4318
题意概述:给定一个长度为n的实数数组,表示每个数是1的概率(不是1就是0),如果有一段长度为a的极长连续1,就可以得$a^3$的分数,求期望得分。
看完这个题感觉OSU大概是个挺有意思的游戏?注:放假后玩了一下发现真的很好玩。再重复一遍:平方的期望不等于期望的平方,立方的期望不等于期望的立方,但是和的期望等于期望的和。这题是SDWC讲过的题目,现在才做,惭愧啊
我们用$dp_i$表示到i这个位置为止的期望分数,它可以怎么转移呢?首先肯定是要加上$dp_{i-1}$的,再来一句绕口令:立方的和的期望等于立方的期望的和。但是还有$a_i$的概率它是可以增加期望的。增加多少呢?考虑对于一串长度为$L$的全$1$串,它的分数是$L^3$,我们往后再加上一个1,他可以额外贡献那些呢?$3*L^2+3*L+1$,就是这么多,但是因为(此处略去几句绕口令),$L^2$,$L$的期望也是要单独维护的,$L^2$还得推式子通过$L$来维护。这两个维护时就不要加上$l_{i-1}$的了,而是必须将这一个位置是1的概率乘进去,因为这样也就等于说是加(完了我在说什么啊)。最后再用我觉得比较清晰的话语解释一下:dp数组相当于是一个前缀和数组,但是l,ll数组仅仅表示当前这里的期望,不涉及前缀和什么的。
1 # include <cstdio> 2 # include <iostream> 3 4 using namespace std; 5 6 const int maxn=100005; 7 int n; 8 double a[maxn]; 9 double dp[maxn],l[maxn],ll[maxn]; 10 11 int main() 12 { 13 scanf("%d",&n); 14 for (int i=1;i<=n;++i) 15 scanf("%lf",&a[i]); 16 for (int i=1;i<=n;++i) 17 { 18 l[i]=a[i]*(l[i-1]+1); 19 ll[i]=a[i]*(ll[i-1]+2*l[i-1]+1); 20 dp[i]=dp[i-1]+a[i]*(3*ll[i-1]+3*l[i-1]+1); 21 } 22 printf("%.1lf",dp[n]); 23 return 0; 24 }
WJMZBMR打osu!:https://www.luogu.org/problemnew/show/P1365
题意概述:这次是求平方和了,有些操作已经打好了,有的凭运气,其实和上一个也差不多。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 5 using namespace std; 6 7 const int maxn=300005; 8 int n; 9 char c; 10 double a[maxn]; 11 double dp[maxn],l[maxn]; 12 13 int main() 14 { 15 scanf("%d",&n); 16 for (int i=1;i<=n;++i) 17 { 18 cin>>c; 19 while (c!='?'&&c!='o'&&c!='x') cin>>c; 20 if(c=='?') a[i]=0.5; 21 if(c=='o') a[i]=1; 22 } 23 for (int i=1;i<=n;++i) 24 { 25 l[i]=a[i]*(l[i-1]+1); 26 dp[i]=dp[i-1]+a[i]*(2*l[i-1]+1); 27 } 28 printf("%.4lf",dp[n]); 29 return 0; 30 }
百事世界杯之旅:https://www.luogu.org/problemnew/show/P1291
题意概述:每次随机抽取一个1-n范围内的数字,求每个数都至少出现过一次的期望抽取数。
设$dp_i$表示目前已经抽到了$i$个数字,那么再抽一次抽到一个新数的概率就是$frac{n-i}{n}$,抽到一个新数所需次数的期望就是$frac{n}{n-i}$,最后的答案就是$dp_n$
1 # include <cstdio> 2 # include <iostream> 3 4 using namespace std; 5 6 int n; 7 long long a,b,c,g; 8 int lena,lenb,lenc; 9 10 long long gcd (long long a,long long b) { return b?gcd(b,a%b):a; } 11 12 int getl (long long x) 13 { 14 int a=0; 15 while (x) { a++; x/=10; } 16 return a; 17 } 18 19 int main() 20 { 21 scanf("%d",&n); 22 a=b=1; 23 for (int i=2;i<=n;++i) 24 { 25 a=a*i+b; 26 b=b*i; 27 g=gcd(a,b); 28 a/=g; 29 b/=g; 30 } 31 a*=n; 32 g=gcd(a,b); 33 a/=g; 34 b/=g; 35 c=a/b; 36 if(c) 37 { 38 if(a%b) 39 { 40 a=a%b; 41 lenc=getl(c); 42 for (int i=1;i<=lenc;++i) 43 printf(" "); 44 printf("%lld ",a); 45 lenb=getl(b); 46 printf("%lld",c); 47 for (int i=1;i<=lenb;++i) 48 printf("-"); 49 printf(" "); 50 for (int i=1;i<=lenc;++i) 51 printf(" "); 52 printf("%lld",b); 53 } 54 else printf("%lld",c); 55 } 56 else 57 { 58 printf("%lld ",a); 59 lena=getl(a); 60 lenb=getl(b); 61 lena=max(lena,lenb); 62 for (int i=1;i<=lena;++i) 63 printf("-"); 64 printf(" "); 65 printf("%lld",b); 66 } 67 return 0; 68 }
---shzr