• 概率与期望题库题目整理


    骰子基础版


    高中数学题,我是暴力做的。
    最多甩24次骰子,可以发现 (6^{24}) 没有爆(unsigned long long),直接快速幂搞定分母(基本事件总数)
    然后考虑>X点的方案,我是稍微转移下,定义dp[i][j]表示前i个骰子总点数是j的方案数,直接转移搞搞即可,分子也搞定了。
    求个gcd约个分,就ok了

    三角形的概率


    高中数学题,结论题。
    答案是:
    puts("0.500")
    证明看这里神犇LZZ的博客

    聪聪和可可


    新加的题,是个期望dp,转移和预处理比较复杂,但是不难理解。
    定义dp[i][j]表示猫在i,鼠在j的期望时间,运用dfs记忆化搜索的形式dp。
    预处理一堆:

    • next[i][j]表示猫在i,鼠在j的猫下一步会走哪(根据题目定义,猫会向离鼠近的编号最小的走,可以预处理)
    • dis[i][j]表示i到j的距离,每个点bfs一遍(n^2)求出
      转移(记得期望dp要倒着推):
      (dp[i][j]=dp[next[next[i][j]][j]][j]*frac{1}{degree[j]+1}+sumlimits_{k}^{k是j的临接点} dp[next[next[i][j]][j]][k]*frac{1}{degree[j]+1})
      这里next两次是因为猫一次走两步,degree[j]+1是因为从j除了到他的临接点,还可以不动,所以老鼠这一步走的总方案数是degree[j]+1。
      dp数组的初始化:对于距离为1、2的两点i,j猫吃鼠时间需要1,距离为0的猫吃鼠时间需要0;
      代码:
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e3+10;
    inline int read(){
    	int x=0;char ch=getchar();
    	while(ch<'0'||ch>'9')ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		x=x*10+ch-'0';
    		ch=getchar();
    	}#include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e3+10;
    inline int read(){
    	int x=0;char ch=getchar();
    	while(ch<'0'||ch>'9')ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return x;
    }
    int next[maxn][maxn],dis[maxn][maxn],degree[maxn];
    int n,m,cat,mouse;
    double dp[maxn][maxn];
    struct E{
    	int to,next;
    }edge[maxn];
    int head[maxn],tot;
    void add(int from,int to){
    	edge[++tot].to=to;
    	edge[tot].next=head[from];
    	head[from]=tot;
    }
    queue<int> q;
    void bfs(int s){
    	for(register int i=head[s];i;i=edge[i].next){
    		int v=edge[i].to;
    		dp[s][v]=1;
    		for(register int j=head[v];j;j=edge[j].next){
    			int w=edge[j].to;
    			if(w==s)continue;
    			dp[s][w]=1;
    		}
    	}
    	q.push(s);dis[s][s]=0;dp[s][s]=0;
    	while(!q.empty()){
    		int u=q.front();q.pop();
    		for(register int i=head[u];i;i=edge[i].next){
    			int v=edge[i].to;
    			if(dis[s][v]>dis[s][u]+1){
    				dis[s][v]=dis[s][u]+1;
    				q.push(v);
    			}
    		}
    	}
    }
    void init(int from,int to){
    	int Min=0x7fffffff;
    	for(register int i=head[from];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(dis[v][to]==Min){
    			if(v<next[from][to]){
    				next[from][to]=v;
    			}
    		}
    		if(dis[v][to]<Min){
    			next[from][to]=v;
    			Min=dis[v][to];
    		}
    	}
    }
    double dfs(int u,int v){
    	if(dp[u][v]!=0||u==v){
    		return dp[u][v];
    	}
    	int now=next[next[u][v]][v];
    	dp[u][v]+=(dfs(now,v)+1)/(degree[v]+1);
    	for(register int i=head[v];i;i=edge[i].next){
    		dp[u][v]+=(dfs(now,edge[i].to)+1)/(degree[v]+1);
    	}
    	return dp[u][v];
    }
    int main(){
    	freopen("cchkk.in","r",stdin);
    	freopen("cchkk.out","w",stdout);
    	n=read();m=read();cat=read();mouse=read();
    	for(register int i=1;i<=m;i++){
    		int from=read(),to=read();
    		add(from,to);add(to,from);
    		degree[from]++;
    		degree[to]++;
    	}
    	memset(dis,0x3f,sizeof(dis));
    	memset(next,0x3f,sizeof(next));
    	for(register int i=1;i<=n;i++){
    		bfs(i);
    	}
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=n;j++){
    			init(i,j);
    		}
    	}
    	printf("%.3lf
    ",dfs(cat,mouse));
    	return 0;
    }
    
    	return x;
    }
    int next[maxn][maxn],dis[maxn][maxn],degree[maxn];
    int n,m,cat,mouse;
    double dp[maxn][maxn];
    struct E{
    	int to,next;
    }edge[maxn];
    int head[maxn],tot;
    void add(int from,int to){
    	edge[++tot].to=to;
    	edge[tot].next=head[from];
    	head[from]=tot;
    }
    queue<int> q;
    void bfs(int s){
    	for(register int i=head[s];i;i=edge[i].next){
    		int v=edge[i].to;
    		dp[s][v]=1;
    		for(register int j=head[v];j;j=edge[j].next){
    			int w=edge[j].to;
    			if(w==s)continue;
    			dp[s][w]=1;
    		}
    	}
    	q.push(s);dis[s][s]=0;dp[s][s]=0;
    	while(!q.empty()){
    		int u=q.front();q.pop();
    		for(register int i=head[u];i;i=edge[i].next){
    			int v=edge[i].to;
    			if(dis[s][v]>dis[s][u]+1){
    				dis[s][v]=dis[s][u]+1;
    				q.push(v);
    			}
    		}
    	}
    }
    void init(int from,int to){
    	int Min=0x7fffffff;
    	for(register int i=head[from];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(dis[v][to]==Min){
    			if(v<next[from][to]){
    				next[from][to]=v;
    			}
    		}
    		if(dis[v][to]<Min){
    			next[from][to]=v;
    			Min=dis[v][to];
    		}
    	}
    }
    double dfs(int u,int v){
    	if(dp[u][v]!=0||u==v){
    		return dp[u][v];
    	}
    	int now=next[next[u][v]][v];
    	dp[u][v]+=(dfs(now,v)+1)/(degree[v]+1);
    	for(register int i=head[v];i;i=edge[i].next){
    		dp[u][v]+=(dfs(now,edge[i].to)+1)/(degree[v]+1);
    	}
    	return dp[u][v];
    }
    int main(){
    	freopen("cchkk.in","r",stdin);
    	freopen("cchkk.out","w",stdout);
    	n=read();m=read();cat=read();mouse=read();
    	for(register int i=1;i<=m;i++){
    		int from=read(),to=read();
    		add(from,to);add(to,from);
    		degree[from]++;
    		degree[to]++;
    	}
    	memset(dis,0x3f,sizeof(dis));
    	memset(next,0x3f,sizeof(next));
    	for(register int i=1;i<=n;i++){
    		bfs(i);
    	}
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=n;j++){
    			init(i,j);
    		}
    	}
    	printf("%.3lf
    ",dfs(cat,mouse));
    	return 0;
    }
    
    

    OSU!

    一道期望dp题,有思考点。
    期望是线性的,上数学课的时候,老师这么跟我们说,我们真正理解这句话了吗,我就没有
    公式:(E(kx+b)=k*E[x]+b)
    我们还知道:期望的平方不等于平方的期望
    公式:(E(x^2) eq E^2(x))
    但是:(E^2(x+1)=E^2(x)+2*E(x)+1)
    别问我证明,但他确实是对的
    这道题让求后缀1长度3次方的期望和(不是异或3啊)
    转化成立方和公式,然后就好求了。
    注意我们的E(x)要定义为“后缀1长度为x的期望”,而不是“后缀1长度为x的期望和”,那个不好转移。
    转移方程就是:
    (E(i)=(E(i-1)+1)*p[i])

    (E2(i)=(E2[i-1]+2*E[i-1]+1)*p[i])

    (dp[i]=(dp[i-1]+3*E2[i-1]+3*E[i-1]+1)*p[i]+(1-p[i])*dp[i-1])

    这个三次方的概率跟之前的不一样是因为它表示的是前i的期望和,是题目中所求。
    比较简单就不放代码了。

    守卫者的挑战


    语文阅读理解题
    本题的题意较难理解,是本题的难点。
    一句话题意:有n项挑战,问通过大于等于L项挑战而且所得背包容量大小大于地图数量的概率
    转移类似背包,dp[i][j][k]表示第i轮,已经赢了k轮当前背包剩余容量为j的概率(这里j是可以为负的),直接转移,没啥好说的。
    记得,第二维可能是负数,需要加一个值保证为正,类似之前考得某个大模拟。
    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=2e2+1;
    double dp[maxn][2*maxn][maxn],p[maxn];
    int mode[maxn];
    int main(){
    	int n,l,k;
    	scanf("%d%d%d",&n,&l,&k);
    	for(int i=1;i<=n;i++){
    		int x;
    		scanf("%d",&x);
    		p[i]=1.0*x/100;
    	}
    	for(int i=1;i<=n;i++){
    		scanf("%d",&mode[i]);
    	}
    	dp[0][k+200][0]=1;
    	for(int i=1;i<=n;i++){
    		for(int j=-200;j<=200;j++){
    			for(int k=0;k<=i;k++){
    				if(mode[i]>=0){
    					if(j-mode[i]>=-200)dp[i][j+200][k]+=p[i]*dp[i-1][j+200-mode[i]][k-1];
    				}else{
    					if(j+200)dp[i][j+200][k]+=p[i]*dp[i-1][j+200+1][k-1];
    				}
    				dp[i][j+200][k]+=(1-p[i])*dp[i-1][j+200][k];
    			}	
    		}
    	}
    	double ans=0;
    	for(int j=0;j<=200;j++){
    		for(int k=l;k<=n;k++){
    			ans+=dp[n][j+200][k];
    		}
    	}
    	printf("%lf",ans);
    	return 0;
    }
    
    

    Easy


    和OSU!是兄弟题,在转移的时候注意下:如果是'o'的话,相当于一个概率为100%的块,'x'的话,相当于一个概率为0%的块,然后这道题是维护后缀1长度平方,比OSU!还简单些。

    单选错位


    有点i思维但是还是很裸的期望dp。
    这道题主要难处理的是每一步的概率,我们可以这样想。
    这道题本来有1~a个选项,你答的是1~b中的答案,那答对的概率就是(frac{min(a,b)}{a*b}),通过公式(P(A)= frac{事件A发生的情况}{基本事件总数})得出
    然后就是裸的dp了,这道题并没有严格的要求从前往后推还是从后往前推,正推即可。
    太裸了不放代码了

    卡牌游戏


    写完这道题感觉对约瑟夫问题的递推公式有了更深的理解。
    这道题其实是一个概率+约瑟夫问题,在一般约瑟夫中,我们每轮隔着几个人干掉一个人是确定的,这道题确实有概率的。
    沿用约瑟夫问题的想法,倒着推。
    定义dp[i][j]还剩i个人,1做庄,第j个人的胜率
    只有一个人的时候,dp[1][1]=1;他胜利的概率是100%。
    然后转移到下一个,枚举上一个转移过来的牌是啥,推出当前这个j是下一轮的哪个人,再由它的概率乘上这张牌的概率,累加到j的答案里面。
    大概是这样的:

    #include<bits/stdc++.h>
    using namespace std;
    double dp[61][61];
    int a[60],n,m;
    int getd(int i,int k){
    	k%=i;
    	return 1+k;
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	for(register int i=1;i<=m;i++)scanf("%d",&a[i]);
    	dp[1][1]=1.0;
    	for(int i=2;i<=n;i++){
    		for(int j=1;j<=i;j++){
    			for(int k=1;k<=m;k++){
    				int d=a[k]%i;
    				if(d==0)d=i;
    	//这里的d就是当前的数字a[k]在i那轮是选中了谁干掉,他的下一号重新标记为1号,多手%一下,可以自己推出来剩i个人,从1开始顺时针第a[k]个是谁。
    	//然后就能推出来j的上一轮,a[k]步之前,它的编号是啥,然后就可以转移了
    	//约瑟夫问题的每轮重新编号的思想这里需要运用的很纯熟。
    				if(d>j)dp[i][j]+=dp[i-1][i-(d-j)]/m;
    				if(d<j)dp[i][j]+=dp[i-1][j-d]/m;
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	printf("%.2lf%% ",dp[n][i]*100);
    	return 0;
    }
    

    换教室


    期望开始跟图论结合了,vvv。
    这道题并不是太难的图论期望题,还是朴素的期望dp,更像dp一些,状态定义和转移比较难。
    先考虑没有概率会怎样。
    定义dp[i][j][0/1]第i段课,已经换了j节课,当前这节课换没换。
    转移就是:
    (dp[i][j][0]=min(dp[i-1][j][1]+d[i-1]+c[i],dp[i-1][j][0]+c[i-1]+c[i]);)
    (dp[i][j][1]=min(dp[i-1][j-1][1]+d[i-1]+d[i],dp[i-1][j-1][0]+c[i-1]+d[i]);)
    考虑加入了概率会怎样。
    每次换课都有可能成功或不成。
    所以我们稍微换一下dp定义:dp[i][j][0/1]第i段课,已经申请换了j节课,当前这节课在哪上的。
    每次涉及到换课时候,就直接分概率算,每次申请换课都有可能成或不成。
    (dp[i][j][0]=min(dp[i-1][j][0]+c[i-1]+c[i],dp[i-1][j][1]+p[i-1]*(d[i-1]+c[i])+(1-p[i-1])*(c[i-1]+c[i]));)
    显然如果不换课,通过率是100%,那就没概率啥事,要换课,就有可能不成功,所以这么转移。
    dp[i][j][1]的转移类似。
    (dp[i][j][1]=min(dp[i][j][1],min(dp[i-1][j-1][0]+p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]],dp[i-1][j-1][1]+p[i]*p[i-1]*dis[d[i-1]][d[i]]+(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]]+(1-p[i])*p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*p[i]*dis[c[i-1]][d[i]]));)
    就是长了一些,类似一些ex的一堆转移柿子的dp题,但是不难理解

    #include<bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int x=0;char ch=getchar();
    	while(ch<'0'||ch>'9')ch=getchar();
    	while(ch>='0'&&ch<='9'){
    		x=x*10+ch-'0';
    		ch=getchar();
    	}
    	return x;
    }
    const int maxn=4.5e3l+10;
    struct E{
    	int to,val,next;
    }edge[5*maxn];
    int head[maxn],tot;
    void add(int from,int to,int val){
    	edge[++tot].to=to;
    	edge[tot].val=val;
    	edge[tot].next=head[from];
    	head[from]=tot;
    }
    double dp[maxn][maxn][2],p[maxn];
    int n,m,v,e,c[maxn],d[maxn];
    int dis[maxn][maxn],vis[maxn],val[maxn][maxn];
    queue<int> q;
    void spfa(int s){
    	dis[s][s]=0;
    	memset(vis,0,sizeof(vis));q.push(s);
    	while(!q.empty()){
    		int u=q.front();q.pop();vis[u]=0;
    		for(int i=head[u];i;i=edge[i].next){
    			int v=edge[i].to;
    			if(dis[s][v]>dis[s][u]+edge[i].val){
    				dis[s][v]=dis[s][u]+edge[i].val;
    				if(!vis[v]){
    					q.push(v);
    					vis[v]=1;
    				}
    			}
    		}
    	}
    } 
    int main(){
    	n=read();m=read();v=read();e=read();
    	for(int i=1;i<=n;i++)c[i]=read();
    	for(int i=1;i<=n;i++)d[i]=read();
    	for(int i=1;i<=n;i++)scanf("%lf",&p[i]);memset(val,0x3f,sizeof(val));
    	memset(dis,0x3f,sizeof(dis));
    	for(int i=1;i<=e;i++){
    		int from=read(),to=read(),val=read();
    		add(from,to,val);add(to,from,val);
    		dis[from][to]=dis[to][from]=min(val,dis[from][to]);
    	}
    	for(int i=1;i<=v;i++){
    		dis[i][i]=0;
    	}
    	for(int i=1;i<=v;i++){
    		for(int j=1;j<=v;j++){
    			for(int k=1;k<=v;k++){
    				dis[j][k]=min(dis[j][k],dis[j][i]+dis[i][k]);
    			}
    		}
    	}	
    	for(int i=1;i<=n;i++){
    		for(int j=0;j<=m;j++){
    			dp[i][j][0]=dp[i][j][1]=0x3f3f3f3f;
    		}
    	}
    	dp[1][0][0]=dp[1][1][1]=0;
    	for(int i=2;i<=n;i++){
    		dp[i][0][0]=dp[i-1][0][0]+dis[c[i-1]][c[i]];
    		for(int j=1;j<=m;j++){
    			int pos1=c[i-1],pos2=d[i-1],pos3=c[i],pos4=d[i];
    			dp[i][j][0]=min(dp[i][j][0],min(dp[i-1][j][0]+dis[pos1][pos3],dp[i-1][j][1]+p[i-1]*dis[pos2][pos3]+(1-p[i-1])*dis[pos1][pos3]));
    			dp[i][j][1]=min(dp[i][j][1],min(dp[i-1][j-1][0]+p[i]*dis[pos1][pos4]+(1-p[i])*dis[pos1][pos3],dp[i-1][j-1][1]+p[i]*p[i-1]*dis[pos2][pos4]+(1-p[i-1])*(1-p[i])*dis[pos1][pos3]+(1-p[i])*p[i-1]*dis[pos2][pos3]+(1-p[i-1])*p[i]*dis[pos1][pos4]));
    		}
    	}
    	double ans=0x3f3f3f3f;
    	for(int j=0;j<=m;j++){
    		ans=min(ans,min(dp[n][j][1],dp[n][j][0]));
    	}
    	printf("%.2lf
    ",ans);
    	return 0;
    }
    

    奖励关


    阅读理解题2
    题意又是不很好理解,而且就算理解了思维量也不小。
    但是还是逃不出期望dp的范畴,好好想想怎么定义,怎么转移,题目是难不倒我们的!
    先翻译题面:
    每次有(frac{1}{n})几率扔出来一个东西,你可以选择捡或不捡(吃还是不吃什么的太奇怪了!)
    然后你每捡一个东西,都会加一个值,值可正可负,然后一个物品被捡有前提条件,就是不满足前提条件就不允许捡ta。
    问最大的期望。
    注意本题n<=15,小的离谱,直接状压搞上,定义dp[i][S]表示第i轮状态为S的期望得分
    我们沿用期望dp倒推的思路,考虑每个i,S怎么被转移。
    枚举每个被扔出来的物品,它会对这次得分造成一定贡献。

    1. 如果被扔出来的物品,已经满足它的前提条件,那么我们可以把它塞到现在的答案里,也可能不塞,用类似背包的思路,直接对两种情况取max转移:
      (dp[i][S]+=sumlimits_{x}^{xin(1~n)}frac{1}{n}max(dp[i+1][S],dp[i+1][S|1<<(x-1)]+score[x]);)
      x是枚举的当前蹦出来的那个。
    2. 如果扔出来的物品,没满足前提条件,那就没法要它的分,直接由dp[i+1][S]转移过来即可。

    然后就没了,直接转移就可以得出正确的答案了。最后答案就是dp[1][0]。
    码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    double dp[110][maxn];
    int pre[maxn],sc[maxn];
    int n,k;
    int main(){
    	scanf("%d%d",&k,&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&sc[i]);
    		int x;
    		while(1){
    			scanf("%d",&x);
    			if(x==0)break;
    			else pre[i]|=(1<<x-1);
    		}
    	}
    	int Max=(1<<n)-1;
    	for(int i=k;i>=1;i--){
    		for(int S=0;S<=Max;S++){
    			for(int j=1;j<=n;j++){
    				int now=1<<j-1;	
    				if((pre[j]&S)==pre[j]){
    					dp[i][S]+=max(dp[i+1][S],(dp[i+1][S|now]+sc[j]))/n;
    				}else{
    					dp[i][S]+=dp[i+1][S]/n;
    				}
    			}
    		}
    	}
    	printf("%.6lf
    ",dp[1][0]);
    	return 0;
    }
    

    游走


    这道题是期望与高斯消元的结合(历史性的会面)之后他俩会经常放在一起ex我们。
    这道题主要的思路就是:
    求边的期望-->求点的期望-->高斯消元求解-->反推边的期望
    几个前备知识:
    首先,我们知道边的期望经过次数,等于它端点的期望经过次数×(1/端点的度数)
    这挺显然的,一个边只有可能由它的两个端点走过来,那它的期望也一定是两个端点的期望乘上他们经过这条边的概率(如题,就是度数分之一)
    接着,我们考虑点的期望怎么求。
    一个点只有可能从与它相邻的点转移过来,所以类比上面的结论,一个点的期望,就等于它的邻接点们的期望,乘以它邻接点各自转移过来的概率。
    所以,综上我们可以得出一个方程式子:(E表示期望)
    (E(u)= sumlimits_{v}^{edge(u,v)in m}frac{1}{degree[v]})
    注意:1和n这两个点需要特判,1这个点作为起点,默认已经经过一次,n这个点到这就不会在走,所以他不会去更新它邻接点的答案。也就是说计算一个点的期望时,如果它的邻接点有n,不算1/degree[n]这个值。
    接下来,考虑得出这个柿子后,怎么求出每个dp的确定值。
    首先:类似dp转移的那种方法肯定不行,每个点都会对别的点有贡献,别的点也会对这个点有贡献,这会出现环,dp转移会转移死循。
    所以这道题求出每个点的期望的方法就是解方程辣。
    根据上面的柿子,我们发现每个点的期望都最多只会加上n-2个点的期望乘概率,而n的范围允许我们(n^3)做,那么就把每个点的转移写成(a*E[1]+b*E[2]+c*E[3]+d*E[4]+……=x)的形式,然后发现这是一个矩阵是(n-1)*n的n-1元方程,(不用算n这个点的期望,因为它不会给点贡献期望,所以也不会给边贡献期望),高斯消元即可。
    最后统计一下每条边的期望值,从大到小排个序,分别标号1~m然后就出结果了。

  • 相关阅读:
    jQuery serialize()方法无法获取表单数据
    gorm 零值不更新问题
    SpringCloud Nacos使用和配置,SpringCloud Nacos 服务注册中心配置使用
    Nacos longPolling error,Nacos1.4.1服务配置文件更新一次后报错
    Windows Mysql5.7安装和配置,Windows 安装多个Mysql
    SpringCloud Gateway使用和配置,SpringCloud Gateway predicates详细配置
    Windows RabbitMQ_3.8 安装和配置,Windows erlang下载
    SpringCloud Hystrix dashboard2.2.7使用和配置,SpringCloud Hystrix dashboard服务监控
    SpringCloud Hystrix使用和配置,SpringCloud Hystrix服务熔断降级
    SpringCloud OpenFeign使用和配置,Java OpenFeign 使用教程
  • 原文地址:https://www.cnblogs.com/liu-yi-tong/p/13819680.html
Copyright © 2020-2023  润新知