概率与期望

A.绿豆蛙的归宿
\(f[x]\) 表示从x走到终点经过的路径的期望长度。
\(x\) 出发经过 \(k\) 条边,则有:

\[f[x]=\frac{1}{k} \sum_{i=1}^{k} (f[y_i]+z_i) \]

由于\(f[N]=0\),所以从终点出发在反图上跑拓扑排序。

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
const int N=200010;
int to[N],edge[N],nxt[N],head[N],in[N],out[N];
int n,m,x,y,z,tot;
double dis[N];
queue<int> q;
void add(int x,int y,int z){
	to[++tot]=y;
	edge[tot]=z;
	nxt[tot]=head[x];
	head[x]=tot;
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>x>>y>>z;
		add(y,x,z);
		in[x]++;
		out[x]++;
	}
	q.push(n);
	while(q.size()){
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=nxt[i]){
			int y=to[i];
			dis[y]+=(dis[x]+edge[i])/in[y];
			out[y]--;
			if(!out[y]) q.push(y);
		}
	}
	printf("%.2f\n",dis[1]);
	return kw;
}

B.扑克牌
\(f[a][b][c][d][x][y]\) 表示从当前状态跳到终点的期望长度,\(a\) 张黑桃,\(b\) 张红桃,\(c\) 张梅花,\(d\) 张方块, \(x\) 为大王状态,\(y\) 为小王状态;
状态转移有:

\[f[a][b][c][d][x][y]= \frac{13-a}{54-sum} \times f[a+1][b][c][d][x][y]+ \frac{13-b}{54-sum} \times f[a][b+1][c][d][x][y]+ \frac{13-c}{54-sum} \times f[a][b][c+1][d][x][y]+ \frac{13-d}{54-sum} \times f[a][b][c][d+1][x][y]+ \frac{1}{54-sum} \times f[a][b][c][d][1-4][y] (if(x==0))+ \frac{1}{54}-sum \times f[a][b][c][d][x][1-4] (if(y==0)) \]

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
int A,B,C,D;
const int N=15;
const double inf=1e20;
double f[N][N][N][N][5][5];
double dp(int a,int b,int c,int d,int x,int y){
	double &ans=f[a][b][c][d][x][y];
	if(ans>=0) return ans;
	int as=a+(x==0)+(y==0);
	int bs=b+(x==1)+(y==1);
	int cs=c+(x==2)+(y==2);
	int ds=d+(x==3)+(y==3);
	if(as>=A&&bs>=B&&cs>=C&&ds>=D) return ans=0;
	int sum=a+b+c+d+(x!=4)+(y!=4);
	sum=54-sum;
	
	if(sum<=0) return ans=inf;
	ans=1;
	if(a<13) ans+=(13.0-a)/sum*dp(a+1,b,c,d,x,y);
	if(b<13) ans+=(13.0-b)/sum*dp(a,b+1,c,d,x,y);
	if(c<13) ans+=(13.0-c)/sum*dp(a,b,c+1,d,x,y);
	if(d<13) ans+=(13.0-d)/sum*dp(a,b,c,d+1,x,y);
	
	if(x==4){
		double t=inf;
		for(int i=0;i<4;i++) t=min(t,1.0/sum*dp(a,b,c,d,i,y));
		ans+=t;
	}
	
	if(y==4){
		double t=inf;
		for(int i=0;i<4;i++) t=min(t,1.0/sum*dp(a,b,c,d,x,i));
		ans+=t;
	}
	return ans;
}
int main(){
	scanf("%d%d%d%d",&A,&B,&C,&D);
	memset(f,-1,sizeof(f));
	double ans=dp(0,0,0,0,4,4);
	if(ans>inf/2) ans=-1;
	printf("%.3lf",ans);
	return kw;
}

C.聪聪和可可
这个题吧还是不太好分析整个过程的,比较麻烦。
大概意思就是猫追老鼠的问题:
猫可以走一步或两步,但猫必须走到离老鼠最近的点,并且如果有编号相同的位置,则取编号最小的点。
猫的位置在 \(i\) ,老鼠的位置在 \(j\)

  1. \(f[i][j]\) 表示猫抓到老鼠的期望步数;
  2. 猫的走位使得不得不先进行预处理,猫的下一步为 \(walk[i][j]\)
  3. 还需要跑最短路求出猫在 \(i\) 到达所有点的最短路径 \(dis[i][j]\) 从而预处理出猫的走位。

再进行状态转移:

  1. 如果 \(i = j\) ,则 \(f[i][j]=0\);
  2. 如果猫直接走一步或两步能够抓到老鼠,则 \(f[i][j]=1\)
  3. 否则的话

\[f[i][j]=\sum_{}dfs(w,z)/(p[j]+1) \]

\(w\) 表示猫走两步到达的位置,\(z\) 表示老鼠走一步或原地不动所到达的位置,\(p[j]\) 表示点\(j\) 的出度。

最后直接记忆化搜索,答案为 \(f[cc][kk]\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,cc,kk,tot,x,y;
int p[N],to[N],head[N],nxt[N];
int dis[N][N],walk[N][N];
double f[N][N];
bool vis[N],vis1[N][N];
queue<int> q;
int read(){
	int ans=0;bool f=0;char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')f=1;ch=getchar();}
	while(ch>='0' && ch<='9'){ans=(ans<<1)+(ans<<3)+(ch^48);ch=getchar();}
	return f?~ans+1:ans;
}

void add(int x,int y){
	to[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}

void spfa(int x){
	memset(dis[x],0x3f,sizeof(dis[x]));
	dis[x][x]=0;
	vis[x]=1;
	q.push(x);
	while(q.size()){
		int top=q.front();
		q.pop();
		vis[top]=0;
		for(int i=head[top];i;i=nxt[i]){
			int y=to[i];
			if(dis[x][y]>dis[x][top]+1){
				dis[x][y]=dis[x][top]+1;
				if(!vis[y]) vis[y]=1;
				q.push(y);
			}
		}
	} 
}

double dfs(int x,int y){
	if(vis1[x][y]) return f[x][y];
	vis1[x][y]=1;
	if(x==y) return f[x][y]=0;
	int k=walk[x][y],w=walk[k][y];
	if(k==y||w==y) return f[x][y]=1;
	f[x][y]=1;
	for(int i=head[y];i;i=nxt[i]){
		int z=to[i];
		f[x][y]+=dfs(w,z)/(p[y]+1);
	}
	f[x][y]+=dfs(w,y)/(p[y]+1);
	return f[x][y];
} 

int main(){
	n=read(),m=read(),cc=read(),kk=read();
	for(int i=1;i<=m;i++){
		x=read(),y=read();
		add(x,y);
		add(y,x);
		p[x]++,p[y]++;
	}
	for(int i=1;i<=n;i++) spfa(i);
	memset(walk,0x3f,sizeof(walk));
	for(int k=1;k<=n;k++){
		for(int i=head[k];i;i=nxt[i]){
			int y=to[i];
			for(int j=1;j<=n;j++){
				if(dis[k][j]==dis[y][j]+1){
					walk[k][j]=min(walk[k][j],y);
				}
			}
		}
	} 
	printf("%.3lf",dfs(cc,kk));
	return 0;
}

D.OSU!
对于每次贡献分数 \(x\) ++,则有:

\[(x+1)^3=x^3+3*x^2+3*x+1 \]

比原分数增加的值为 \(3*x^2+3*x+1\)
所以要维护这个增加值的期望值,由于三次方并不具有线性,所以用一次方和二次方的线性关系来表示三次方,则有:
\(x^1[i]=(x^1[i-1]+1) \times chance[i]\)
\(x^2[i]=(x^2[i-1]+2 \times x^1[i-1]+1) \times chance[i]\)
\(x^3[i]=x^3[i-1]+(3 \times x^2[i-1]+3 \times x^1[i-1]+1) \times chance[i]\)
最终答案为 \(x^3[n]\)

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
const int N=100010;
int n,res;
double chance[N],x1[N],x2[N],ans[N];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lf",&chance[i]);
	for(int i=1;i<=n;i++){
		x1[i]=(x1[i-1]+1)*chance[i];
		x2[i]=(x2[i-1]+2*x1[i-1]+1)*chance[i];
		ans[i]=ans[i-1]+(3*(x1[i-1]+x2[i-1])+1)*chance[i];
	}
	printf("%.1lf",ans[n]);
	return kw;
}

E.Red is good
\(f[i][j]\) 表示选择 \(i\) 张红牌,\(j\) 张黑牌的最优期望;
当翻出全是红牌,则 \(f[i][0]=1\),反之全是黑牌时,则 \(f[0][i]=0\)
状态转移:

\[f[i][j]=max(0, \frac{i}{i+j} \times (f[i-1][j]+1)+ \frac{j}{i+j} \times (f[i][j-1]-1)) \]

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
const int N=5010;
int red,black;
double f[N][N];
int main(){
	scanf("%d%d",&red,&black);
	for(int i=1;i<=red;i++){
		f[i][0]=i;
		for(int j=1;j<=black;j++){
			f[i][j]=max(1.0*0,1.0*i/(i+j)*(f[i-1][j]+1)+1.0*j/(i+j)*(f[i][j-1]-1));
		}	
	}
	int ans=f[red][black]*1e6;
	printf("%lf",(double)ans/1e6);
	return kw;
}

F.守卫者的挑战

\[f_{i,j,k}=\text{成功的期望值} \]

\(i\) 为总挑战数,\(j\) 为胜利场数,\(k\) 为背包容量。

\(k>0\) 代表背包仍有剩余的空间,\(k<0\) 代表目前仍有 \(-k\) 个地图残片还未装入,由于下标不能为负数且 \(-200 \le k \le 200\),所以直接将 \(k\) 加上 \(200\) 即可。

每次共有两种决策:若第 \(i\) 次挑战胜利

\[f_{i,j,k} = \sum_{i = 1}^{n} f_{i-1,j-1,k-a_i} \times p_i \]

若第 \(i\) 次挑战失败

\[f_{i,j,k}= \sum_{i = 1}^{n} f_{i-1,j,k} \times (1-p_i) \]

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
const int N=210;
int n,l,k,a[N];
double ans,p[N],f[N][N][N*2];
int main(){
	scanf("%d%d%d",&n,&l,&k); 
	for(int i=1;i<=n;i++){
		scanf("%lf",&p[i]);
		p[i]=1.0*p[i]/100;
	}
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	f[0][0][200+min(n,k)]=1;//注意边界
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i;j++){
			for(int k=0;k<=400;k++){
				f[i][j][k]+=f[i-1][j][k]*(1-p[i]);
				if(j>0&&k-a[i]>=0) f[i][j][k]+=f[i-1][j-1][k-a[i]]*p[i];
			}
		}
	}
	for(int i=l;i<=n;i++)
		for(int j=200;j<=400;j++)
			ans+=f[n][i][j];
	printf("%.6lf",ans);
	return kw;
} 

G.Easy
与C题思路大同小异
\(l[i]\) 表示枚举期望能达到连续的 \(o\) 的个数,状态转移则有:

\[l[i]=l[i-1]+1 \]

\[f[i]=f[i-1]+2 \times l[i-1]+1 \]

点击查看代码
#include <bits/stdc++.h>
#define kw 0 
using namespace std;
const int N=3e5+10;
int n,m;
double f[N],l[N];
char s[N];
int main(){
	scanf("%d",&n);
	cin>>s+1;
	f[0]=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='o'){
			l[i]=l[i-1]+1;
			f[i]=f[i-1]+2*l[i-1]+1;
		}
		else if(s[i]=='x'){
			f[i]=f[i-1];
		}
		else{
			l[i]=(l[i-1]+1.0)/2.0;
			f[i]=f[i-1]+l[i-1]+0.5;
		}
	}
	printf("%.4lf",f[n]);
	return kw;
}

H.单选错位
感觉比较有意思的一道思维题;
在错位的两个题中,要取选项数小的值作为概率的分子,则有:

\[ans= \sum_{i=1}^{i=n-1} \frac{min(a[i],a[i+1])}{a[i]*a[i+1]} \]

点击查看代码
#include <bits/stdc++.h>
#define kw 0
using namespace std;
const int N=1e7+10;
int n,A,B,C,a[N];
double ans;
int main(){
	scanf("%d%d%d%d%d",&n,&A,&B,&C,a+1);
	for (int i=2;i<=n;i++)
	a[i]=((long long)a[i-1]*A+B)%100000001;
	for(int i=1;i<=n;i++)
	a[i]=a[i]%C+1;
	
	for(int i=1;i<=n-1;i++) ans+=(1.0*min(a[i],a[i+1])/(1.0*a[i]*a[i+1]));
	ans+=(1.0*min(a[1],a[n])/(1.0*a[1]*a[n]));
	printf("%.3lf",ans);
	return kw;
}

持续更新······

posted @ 2024-05-27 21:23  __kw  阅读(22)  评论(0)    收藏  举报