期望与期望DP

Part 0 听雨闲话

雨终于停了……我的总结还没写完

Part 1 什么是"期望"?


数学期望(又称期望、平均值、一阶矩)是随机变量对概率分布的加权平均,表示随机变量的平均值。对于离散的概率分布,数学期望是随机变量取值与相应概率乘积的求和(摘自百度百科)

换句话说:

期望是我们对某一概率事件结果相对理性的估计:在平均计算的理想状态下,这一事件的结果可以被视为多少?

数学定义:

\[E(X)=\sum_{i=1}^{n} (a_{i}*p_{i}) \]

举例:

丢一颗骰子,获得点数的期望是多少?

\[E(X)= \frac{1}{6} *1 +\frac{1}{6} *2 +\frac{1}{6} *3 +\frac{1}{6} *4 +\frac{1}{6} *5 +\frac{1}{6} *6=3.5 \]

也就是说: 当投掷骰子的次数足够大时,我们可以将每一次投掷的结果估计为3.5

Another举例:

发行一种彩票,该彩票的原理如下:
每张彩票3元,购买后随机生成一个六位数,将其与假定的中奖号码529222比较:

  • 末位数字相同:获得1元
  • 末2位数字相同:获得10元
  • 末3位数字相同:获得100元
  • 末4位数字相同:获得1000元
  • 末5位数字相同:获得10000元
  • 全部6位数字相同:获得100000元

请问,对于每张彩票,彩票公司期望的收益是多少?


答案:

\[E(cost)= \frac{1}{10}*1 + \frac{1}{100}*10 + \frac{1}{1000}*100 + \frac{1}{10000}*1000 + \frac{1}{100000}*10000 + \frac{1}{1000000}*100000 = 0.6 \]

即对于每张彩票,公司期望支付0.6元的奖金,则每张彩票收入2.4元。
看来发行彩票真是稳赚不赔的生意。

于是,现在可以用自己的话重新给 "期望" 定义了:

对于一种可能性事件,我们将对应的结果加权概率取平均值,以描述单个事件的平均结果

Part 2 期望DP


例题:P1937 传传爆
有一个 n×m 的矩阵,左上角的格子记为 (1,1),右下角的格子记为 (n,m)。
格子要么是黑色的,要么是白色的。魔法少女从 (1,1) 出发寻找食物,按照如下规则移动:

  • 每次可以向上下左右移动一步,但是不能出界。
  • 如果移动到黑色格子,则无事发生。
  • 如果移动到白色格子,魔法少女会被等概率独立随机传送到任意一个白色格子(包括她所在的格子)上。
  • 传送完后可以继续移动。

当魔法少女到达 (n,m) 时,她会停止移动。魔法少女会最小化移动的期望次数。求出魔法少女移动的期望步数

分析:
考虑经典的网格DP问题:由于最小化移动步数,只能向右或者向下走。
因此,每一格的步数应从其左侧和上方的格子继承。
对于黑格和白格,应分别讨论:

1.黑格

依据题意,走到黑格时无事发生
因此,黑格的步数完全从左侧和上方的格子继承而来。
形式化地,有:$$step[i][j]=min(step[i-1][j],step[i][j-1])+1$$

2.白格

每次到达白格时,会触发随机传送,到达任意一个白格上。
因此,所有的白格可以视为一个整体:不管从何处进入白格,其效果都是等价的——随机到达任意一个白格上。
由此可得,所有白格的步数代价应为一个相同的值,记为E,代表进入白格,经过某些操作后离开白格,然后前往终点的平均期望步数。现在考虑一种策略,使得离开白格后前往终点的期望步数最优。

3.怎么走?

经过分析,不难想到以下几个规则:

  • 不能离开白格后,走几步黑格,再次进入白格。
    • 所有白格为一个整体,离开白格再次进入相当于兜了一个圈子,明显不优。
    • 换言之,一旦决定离开白格,只能一路走黑格到达终点。
    • 因此维护数组T,存储从每个格子处只走黑格到达终点的步数。
  • 如果当前被传送到的白格位置不优,无法只走黑格到终点/终点太远,则我们考虑再次传送。
    • 由于所有白格到终点的期望步数都为E,如果当前格子到终点的黑格距离T大于进入下一个白格的距离t+从下一个白格到终点的距离E,则重新进入白格传送。
  • 如要再次传送,只有两种方案最优:
    • 到达相邻的一个白格(如果有)
    • 否则,走到相邻的黑格,再回到刚刚的白格
4.计算E的值

回顾公式:

\[E(X)=\sum_{i=1}^{n} (a_{i}*p_{i}) \]

这个公式中有两个参数:a(距离终点最优步数)p(概率)
由于每格等概率传送,无需考虑p,将公式变形为:

\[E=\frac{\sum_{i=1}^{N} a_{i}}{N} \]

  • 其中N为白格总数

由刚刚的规则可知:当前白格与终点距离=min(到达一个白格距离+白格期望距离,走黑格到终点距离)
形式化地,有:

\[a_{i} =min(t_{i} + E , T_{i}) \]

将该式带入刚刚的式子,有:

\[E=\frac{\sum_{i=1}^{N}min(T_{i},t_{i}+E)}{N} \]

\[d_{i}=T_{i}-t_{i} \]

\[E=\frac{\sum_{i=1}^{N}t_{i}+min(d_{i},E)}{N} \]

发现这个式子两边都有E,无法对E求值,且式子右边E在min函数里,无法直接移项。
因此,考虑分段"拆掉"min函数:
设分段点为p,对p进行枚举:

\[NE=\sum_{i=1}^{n}t_{i}+\sum_{i=1}^{p}d_{i}+(N-p)E \]

\[E=\frac{\sum_{i=1}^{N}t_{i}+\sum_{i=1}^{p}d_{i}}{p} \]

从1~N遍历枚举p,当满足:

\[d_{p}≤E_{p}<d_{p+1} \]

则此情况成立,得以计算出E。

参考代码实现:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9+7;
int Times,m,n,cnt;
int f[1005][1005],Tmp[1005][1005],Tmp2[1005][1005];
bool vis[1005][1005];
int dx[]={1,-1,0,0};
int dy[]={0,0,-1,1};
struct node{
	int x,y,step;
};
struct white{
	int T,t,d;
}w[1000005];
queue<node>q;
void bfs(node x,int Tmp[][1005])//广搜预处理Tmp数组
{
	while(!q.empty())q.pop();
	memset(vis,0,sizeof(vis));
	
	if(f[x.x][x.y]==1)return;
	
	q.push(x);
	vis[x.x][x.y]=1;
	Tmp[x.x][x.y]=x.step;
	
	while(!q.empty())
	{
		node tmp=q.front();
		q.pop();
		
		for(int k=0;k<4;k++)
		{
			int nx=tmp.x+dx[k];
			int ny=tmp.y+dy[k];
			if(nx<1||nx>n||ny<1||ny>m)continue;
			if(f[nx][ny]==1||vis[nx][ny])continue;
			vis[nx][ny]=1;
			Tmp[nx][ny]=tmp.step+1;
			q.push((node){nx,ny,tmp.step+1});
		}
	}
}
int minn(int a,int b,int c,int d)
{
	int res=inf;
	if(a>=0&&a<res)res=a;
	if(b>=0&&b<res)res=b;
	if(c>=0&&c<res)res=c;
	if(d>=0&&d<res)res=d;
	return res;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>Times;
	while(Times--)
	{
		cin>>n>>m;
		cnt=0;
		//初始化
		for(int i=0;i<=n+1;i++)
			for(int j=0;j<=m+1;j++)
			{
				f[i][j]=0;
				Tmp[i][j]=-1;
				Tmp2[i][j]=-1;
			}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				char c;
				cin>>c;
				if(c=='C')f[i][j]=0;
				else f[i][j]=1;
			}
		}
		//特判:n=m=1
		if(n==1&&m==1)
		{
			cout<<"0/1\n";
			continue;
		}
		//从终点BFS
		bfs((node){n,m,0},Tmp);
		//从起点BFS
		bfs((node){1,1,0},Tmp2);
		//如果没有白色格子
		bool has_white=false;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(f[i][j]==1)has_white=true;
		if(!has_white)
		{
			if(Tmp[1][1]!=-1)cout<<Tmp[1][1]<<"/1\n";
			continue;
		}
		//预处理w数组
		cnt=0;
		long long sum_t=0;
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(f[i][j]==1)
				{
					cnt++;
					//计算t
					bool adj_white=false;
					for(int k=0;k<4;k++)
					{
						int nx=i+dx[k];
						int ny=j+dy[k];
						if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&f[nx][ny]==1)
						{
							adj_white=true;
							break;
						}
					}
					w[cnt].t=adj_white?1:2;
					sum_t+=w[cnt].t;
					
					//计算T:到终点的最短距离
					w[cnt].T=inf;
					for(int k=0;k<4;k++)
					{
						int nx=i+dx[k];
						int ny=j+dy[k];
						if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&f[nx][ny]==0)
						{
							if(Tmp[nx][ny]!=-1)
								w[cnt].T=min(w[cnt].T,Tmp[nx][ny]+1);
						}
					}
					
					if(w[cnt].T==inf)w[cnt].T=1e9;
					w[cnt].d=w[cnt].T-w[cnt].t;
				}
			}
		}	
		//按d排序
		sort(w+1,w+cnt+1,[](const white& a,const white& b){
			return a.d<b.d;
		});
		//求前缀和
		vector<long long>sum_d(cnt+1,0);
		for(int i=1;i<=cnt;i++)
			sum_d[i]=sum_d[i-1]+w[i].d;
		//计算期望E
		long long PE=-1,P=-1;
		for(int p=1;p<=cnt;p++)
		{
			long long nu=sum_d[p]+sum_t;
			long long de=p;
			//检查条件:w[p].d<=E<w[p+1].d
			bool valid=true;
			if((__int128)nu<(__int128)de*w[p].d)valid=false;
			if(p<cnt&&(__int128)nu>=(__int128)de*w[p+1].d)valid=false;	
			if(valid)
			{
				PE=nu;
				P=de;
				break;
			}
		}
		//计算最终答案
		long long up=1e18,down=1;
		//方案1:直接从起点走黑格到终点
		if(Tmp[1][1]!=-1)
		{
			up=Tmp[1][1];
			down=1;
		}
		//方案2:从起点走到白格,然后经过白格系统
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				if(f[i][j]==1)
				{
					//计算从起点到这个白格的距离
					int dist=inf;
					for(int k=0;k<4;k++)
					{
						int nx=i+dx[k];
						int ny=j+dy[k];
						if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&f[nx][ny]==0)
						{
							if(Tmp2[nx][ny]!=-1)
								dist=min(dist,Tmp2[nx][ny]+1);
						}
					}	
					if(dist!=inf)
					{
						//比较:dist+P E/P
						long long upp=(long long)dist*P+PE;
						long long downn=P;
						
						if((__int128)upp*down<(__int128)up*downn)
						{
							up=upp;
							down=downn;
						}
					}
				}
			}
		}	
		//化简
		long long g=__gcd(up,down);
		up/=g;
		down/=g;
		cout<<up<<"/"<<down<<"\n";
	}
	
	return 0;
}
posted @ 2026-08-17 13:42  灿若星尘  阅读(5)  评论(0)    收藏  举报