关于概率期望 DP,首先要了解概率及期望的一些基本定义概率 & 期望。
我们需要用到的比较重要的公式就是期望的定义和期望的线性性质。
定义
即期望等于概率乘权值。需要注意的是,
例如,现在我们掷两枚骰子,我们求掷出的点数之和的期望。那么随机变量
性质
期望是线性函数,满足:
首先要了解,随机变量和函数不同的地方,函数相加定义域取交集,而两个随机变量相加它们的定义域样本空间应该是取笛卡尔积。
如此,我们便能证明这条性质:
其中第三步到第四步将
好了,现在开始讲概率 DP,有很多存在后效性的 DP 也是概率 DP。
例 1
一个
解析:这是一个图中求到某一点的期望步数的套路题,我们尝试从定义严格来推式子。
假设
那么可得出:
其中
接着推:
这里把路径
发现
发现
分配律,然后发现
可以发现
于是得到了期望步数的套路式子:
对于这道题
/*
* @Author: clorf
* @Date: 2020-09-07 21:45:46
* @Last Modified by: clorf
* @Last Modified time: 2020-09-07 22:47:27
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<queue>
#include<cassert>
#define INF 1e9
using namespace std;
const int maxn=310;
const double Pi=acos(-1.0);
// 数组一定要算好开!!!!边和点的关系
// 记得看开不开long long!!!
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
// n和m不要搞混!!!任何循环时候都要注意!!
int n,m,deg[maxn];
int sx,sy,dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
bool mark[maxn][maxn];
char s[maxn][maxn];
double a[maxn][maxn],ans[maxn],eps=1e-8;
inline int point(int x,int y)
{
return (x-1)*m+y;
}
queue<pair<int,int> > q;
inline void bfs()
{
while(!q.empty())
{
int nowx=q.front().first;
int nowy=q.front().second;
q.pop();
for(int i=0;i<=3;i++)
{
int tx=nowx+dir[i][0];
int ty=nowy+dir[i][1];
if(tx<1||tx>n||ty<1||ty>m||mark[tx][ty]||s[tx][ty]=='#')
continue;
mark[tx][ty]=1;
q.push(make_pair(tx,ty));
}
}
}
inline bool gauss()
{
int i=1,j=1;
while(i<=point(n,m)&&j<=point(n,m))
{
int maxx=i;
for(int k=i;k<=point(n,m);k++)
if(fabs(a[k][j])>fabs(a[maxx][j]))
maxx=k;
if(fabs(a[maxx][j])>=eps)
{
swap(a[i],a[maxx]);
for(int k=i+1;k<=point(n,m);k++)
{
double num=a[k][j]/a[i][j];
for(int l=j;l<=point(n,m)+1;l++)
if(fabs(a[i][l])>=eps)
a[k][l]-=num*a[i][l];
}
i++;
}
j++;
}
for(int k=i;k<=point(n,m);k++)
if(fabs(a[k][point(n,m)+1])>=eps)
return 0;
i--;
for(int k=i;k>=1;k--)
{
for(j=1;j<=point(n,m);j++)
if(fabs(a[k][j])>=eps)
break;
ans[j]=a[k][point(n,m)+1]/a[k][j];
for(int l=1;l<k;l++)
a[l][point(n,m)+1]-=ans[j]*a[l][j];
}
return 1;
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(mark,0,sizeof(mark));
memset(a,0,sizeof(a));
memset(ans,0,sizeof(ans));
memset(deg,0,sizeof(deg));
memset(s,0,sizeof(s));
for(int i=1;i<=n;i++)
{
scanf("\n");
for(int j=1;j<=m;j++)
{
scanf("%c",&s[i][j]);
if(s[i][j]=='@')
{
sx=i;
sy=j;
}
if(s[i][j]=='$')
{
mark[i][j]=1;
q.push(make_pair(i,j));
}
}
}
bfs();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(!mark[i][j]||s[i][j]=='#')
continue;
if(s[i][j]=='$')
{
a[point(i,j)][point(i,j)]=1;
a[point(i,j)][point(n,m)+1]=0;
continue;
}
for(int k=0;k<4;k++)
{
int tx=i+dir[k][0];
int ty=j+dir[k][1];
if(!mark[tx][ty]||s[tx][ty]=='#'||tx<1||tx>n||ty<1||ty>m)
continue;
deg[point(i,j)]++;
a[point(i,j)][point(tx,ty)]=1.0;
}
a[point(i,j)][point(i,j)]=-deg[point(i,j)];
a[point(i,j)][point(n,m)+1]=-deg[point(i,j)];
}
bool flag=gauss();
if(mark[sx][sy]&&flag)
printf("%.6lf\n",ans[point(sx,sy)]);
else
printf("-1\n");
}
return 0;
}
/*
2 2
@#
.$
*/
例 2
二维方格,给出每个点向四方走的概率,保证和为
解析:就是上道题那个套路的模板题,直接推式子高斯消元。
/*
* @Author: clorf
* @Date: 2020-09-11 09:22:02
* @Last Modified by: clorf
* @Last Modified time: 2020-09-11 11:50:49
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#define INF 1e9
using namespace std;
const int maxn=45;
const double Pi=acos(-1.0);
const double eps=1e-7;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int n,m,dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}},r;
double p[maxn][maxn][4],a[maxn*maxn][maxn*maxn];
inline int point(int x,int y)
{
if(x<1||x>n||y<1||y>m)
return n*m+2;
return (x-1)*m+y;
}
inline void gauss(int m,int n)
{
int i=m,j=n;
while(i>=1&&j>=1)
{
if(fabs(a[i][j]))
{
int downi=max(i-r,1);
for(int k=i-1;k>=downi;k--)
{
double num=a[k][j]/a[i][j];
int downj=max(j-r,1);
for(int l=j;l>=downj;l--)
a[k][l]-=a[i][l]*num;
a[k][n+1]-=a[i][n+1]*num;
}
i--;
}
j--;
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF&&n+m)
{
r=m;
memset(a,0,sizeof(a));
for(int k=0;k<4;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%lf",&p[i][j][k]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int s=point(i,j);
a[s][s]=1.0;
if(i==n&&j==m)
continue;
a[s][point(n,m)+1]=1.0;
for(int k=0;k<4;k++)
a[s][point(i+dir[k][0],j+dir[k][1])]=-p[i][j][k];
}
gauss(point(n,m),point(n,m));
printf("%lf\n",a[1][n*m+1]/a[1][1]);
}
return 0;
}
例 3
跟上题相同,把步长改成
解析:跟上题套路一样。。。只不过把边权变成
由于只有向右和向下的方向,所以这时前面的(左上的)状态都可以由后面的(右下的)状态得到,直接递推即可,不用高斯消元。
例 4
无向带权图,求从
解析:自然而然地想到用刚才地定义式子一样来推。但是有一个问题,我们会在这一步地时候卡住:
我们原套路中是加法,因此可以用分配律分开,但在这里是异或运算,异或并不满足分配律。简单来说,即异或的期望不等同于期望的异或。
这时候我们就需要换状态了。由于异或各位之间不互相影响,我们设
然后对每一位高斯消元即可,答案即为
/*
* @Author: clorf
* @Date: 2020-09-08 11:36:59
* @Last Modified by: clorf
* @Last Modified time: 2020-09-08 20:38:53
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#define INF 1e9
using namespace std;
const int maxn=210;
const double Pi=acos(-1.0);
// 数组一定要算好开!!!!边和点的关系
// 记得看开不开long long!!!
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
// n和m不要搞混!!!任何循环时候都要注意!!
int n,m;
int head[maxn],cnt,maxx,deg[maxn];
double a[maxn][maxn],ans[maxn],sum,eps=1e-8;
struct node
{
int next;
int to;
int num;
}e[20010];
void add(int from,int to,int num)
{
e[++cnt].next=head[from];
e[cnt].to=to;
e[cnt].num=num;
head[from]=cnt;
deg[from]++;
}
inline void connect(int x)
{
a[n][n]=1;
for(int u=1;u<n;u++)
{
a[u][u]=deg[u];
for(int j=head[u];j;j=e[j].next)
{
int v=e[j].to;
if((e[j].num&x)==0)
a[u][v]--;
else
{
a[u][v]++;
a[u][n+1]++;
}
}
}
}
inline void gauss()
{
int i=1,j=1;
while(i<=n&&j<=n)
{
int maxx=i;
for(int k=i+1;k<=n;k++)
if(fabs(a[k][j])>fabs(a[maxx][j]))
maxx=k;
if(fabs(a[maxx][j]))
{
swap(a[maxx],a[i]);
for(int k=i+1;k<=n;k++)
{
double num=a[k][j]/a[i][j];
for(int l=j;l<=n+1;l++)
a[k][l]-=num*a[i][l];
}
i++;
}
j++;
}
for(i=n;i>=1;i--)
{
for(j=i+1;j<=n;j++)
a[i][n+1]-=ans[j]*a[i][j];
ans[i]=a[i][n+1]/a[i][i];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
if(a!=b)
add(b,a,c);
maxx=max(maxx,c);
}
for(int i=1;i<=maxx;i<<=1)
{
memset(a,0,sizeof(a));
memset(ans,0,sizeof(ans));
connect(i);
gauss();
sum+=ans[1]*i;
}
printf("%.3lf",sum);
return 0;
}
例 5
现在有一个
- 队列保持不变,有
的概率发生; - 队列第一个人移到队尾,其他人前进一个位置,有
的概率发生; - 第一个人离开队列,其他人前进一个位置,有
的概率发生; - 队列冻结,永远保持现在状态不变,有
的概率发生。
满足
现在要求在你离开队列之前,队列已冻结,并且你在第前
解析:设
稍微分类讨论一下,很容易推出转移式:
我们发现,如果确定了
/*
* @Author: clorf
* @Date: 2020-09-09 09:30:25
* @Last Modified by: clorf
* @Last Modified time: 2020-09-09 11:05:44
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#define INF 1e9
using namespace std;
const int maxn=2010;
const double Pi=acos(-1.0);
// 数组一定要算好开!!!!边和点的关系
// 记得看开不开long long!!!
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
// n和m不要搞混!!!任何循环时候都要注意!!
int n,m,k;
double p1,p2,p3,p4,f[maxn][2];
double a,b[maxn],c[maxn],A,B,C,eps=1e-8;
int main()
{
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
memset(f,0,sizeof(f));
scanf("%lf%lf%lf%lf",&p1,&p2,&p3,&p4);
if(p4<=eps)
{
printf("0.00000\n");
continue;
}
f[0][1]=p4/(p3+p4);
for(int j=1;j<=n;j++)
{
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));//系数
a=A=B=C=0.0;
a=p2/(1-p1);
b[0]=0.0;
c[0]=p4/(1-p1);
for(int i=1;i<=j;i++)
{
b[i]=p3/(1-p1)*f[i-1][(j%2)^1];
if(i<=k-1)
c[i]=p4/(1-p1);
else
c[i]=0.0;
}
A=a;
B=b[j-1];
C=c[j-1];
for(int i=j-2;i>=0;i--)
{
B+=A*b[i];
C+=A*c[i];
A*=a;
}
f[j-1][j%2]=(B+C)/(1-A);
f[0][j%2]=a*f[j-1][j%2]+b[0]+c[0];
for(int i=1;i<=j-2;i++)
f[i][j%2]=a*f[i-1][j%2]+b[i]+c[i];
}
printf("%.5lf\n",f[m-1][n%2]);
}
return 0;
}
例 6
一个无向图,节点
Luogu2973 Driving Out the Piggies G
解析:容易发现这道题和上道题都有一个共同点,它们都是起始状态唯一,但终止状态有多个的问题,但这道题最大的不同点是到达终止状态后立马结束,并且能结束程序的只有终止状态,而上道题在终止状态内还可以继续转移,并且在任何一个状态都可以结束程序。对于这类题,求在每个终止节点终止的概率,也是一类套路问题,我们一般设
其中
我们发现到这里我们不能像之前那样把路径拆开处理了,我们需要借助其他方法。
我们设
左式和右式都是求的是
于是我们可以继续处理式子:
发现我们上面那个式子转化的困难点就是不知道
接下来的难点就是
其中
于是接着化简期望式子:
枚举所有转移到
在这道题中
于是得出结论式:
于是这道题就直接套式子,高斯消元即可。对每个终点,注意不仅要在那一点停止,还要爆炸,最后在乘以爆炸的概率即可。
/*
* @Author: clorf
* @Date: 2020-09-11 15:10:06
* @Last Modified by: clorf
* @Last Modified time: 2020-09-11 15:39:11
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<vector>
#define INF 1e9
using namespace std;
const int maxn=310;
const double Pi=acos(-1.0);
const double eps=1e-8;
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
int n,m,P,Q;
double p,a[maxn][maxn],ans[maxn];
vector<int> e[maxn];
inline void gauss(int m,int n)
{
int i=1,j=1;
while(i<=m&&j<=n)
{
int maxx=i;
for(int k=i+1;k<=m;k++)
if(fabs(a[k][j])>fabs(a[maxx][j]))
maxx=k;
if(fabs(a[maxx][j])>eps)
{
swap(a[maxx],a[i]);
for(int k=i+1;k<=m;k++)
{
double num=a[k][j]/a[i][j];
for(int l=j;l<=n+1;l++)
a[k][l]-=num*a[i][l];
}
i++;
}
j++;
}
for(i=n;i>=1;i--)
{
for(j=i+1;j<=n;j++)
a[i][n+1]-=a[i][j]*ans[j];
ans[i]=a[i][n+1]/a[i][i];
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&P,&Q);
p=1.0*P/Q;
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
e[a].push_back(b);
e[b].push_back(a);
}
for(int u=1;u<=n;u++)
{
a[u][u]=1.0;
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i];
a[u][v]=(p-1.0)/e[v].size();
}
if(u==1)
a[u][n+1]=1.0;
}
gauss(n,n);
for(int i=1;i<=n;i++)
{
ans[i]*=p;
printf("%.9lf\n",ans[i]);
}
return 0;
}
例 7
无向连通图,有两个人分别在
解析:跟上题一样的套路题。分
同样的,起点
/*
* @Author: clorf
* @Date: 2020-09-09 21:27:03
* @Last Modified by: clorf
* @Last Modified time: 2020-09-10 21:31:53
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#define INF 1e9
using namespace std;
const int maxn=22;
const double Pi=acos(-1.0);
// 数组一定要算好开!!!!边和点的关系
// 记得看开不开long long!!!
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
// n和m不要搞混!!!任何循环时候都要注意!!
int n,m,a,b,cnt;
int deg[maxn];
bool dis[maxn][maxn];
double p[maxn],map[maxn*maxn][maxn*maxn],ans[maxn*maxn];
inline int point(int x,int y)
{
return (x-1)*n+y;
}
inline void gauss(int m,int n)
{
int i=1,j=1;
while(i<=m&&j<=n)
{
int maxx=i;
for(int k=i+1;k<=m;k++)
if(fabs(map[k][j])>fabs(map[maxx][j]))
maxx=k;
if(fabs(map[maxx][j]))
{
swap(map[maxx],map[i]);
for(int k=i+1;k<=m;k++)
{
double num=map[k][j]/map[i][j];
for(int l=j;l<=n+1;l++)
map[k][l]-=num*map[i][l];
}
i++;
}
j++;
}
for(i=n;i>=1;i--)
{
for(int j=i+1;j<=n;j++)
map[i][n+1]-=map[i][j]*ans[j];
ans[i]=map[i][n+1]/map[i][i];
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&a,&b);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
dis[x][y]=dis[y][x]=1;
dis[x][x]=dis[y][y]=1;
deg[x]++;
deg[y]++;
}
for(int i=1;i<=n;i++)
scanf("%lf",&p[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int s=point(i,j);
map[s][s]=-1;
for(int t=1;t<=n*n;t++)
{
int y=(t-1)%n+1;
int x=(t-y)/n+1;
if(x==y)
continue;
if(x==i&&y==j)
map[s][t]+=p[i]*p[j];
else if(x==i&&dis[j][y])
map[s][t]+=p[i]*(1-p[y])/deg[y];
else if(y==j&&dis[i][x])
map[s][t]+=p[j]*(1-p[x])/deg[x];
else if(dis[i][x]&&dis[j][y])
map[s][t]+=(1-p[y])*(1-p[x])/(deg[x]*deg[y]);
}
}
map[point(a,b)][point(n,n)+1]=-1;
gauss(n*n,n*n);
for(int i=1;i<=n;i++)
printf("%.6lf ",ans[point(i,i)]);
return 0;
}
例 8
解析:很明显,根据倒序和小于乱序和小于正序和的定理,边的期望经过次数多的编号应该越小。我们将边的期望经过次数从大到小排序,然后依次按从
边的期望经过次数由点的期望经过次数得到:
然后用套路高斯消元求出点的期望经过次数即可。
/*
* @Author: clorf
* @Date: 2020-09-10 20:29:56
* @Last Modified by: clorf
* @Last Modified time: 2020-09-10 22:24:38
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<vector>
#define INF 1e9
using namespace std;
const int maxn=510,maxm=125010;
const double eps=1e-7;
const double Pi=acos(-1.0);
//1.数组开小
//2.没用long long/爆了类型存储范围
//3.写之前式子没推清楚
//4.变量写错(想当然typo/没想清楚含义)
//5.写之前没想清楚复杂度
template<class T>void read(T &x)
{
x=0;int f=0;char ch=getchar();
while(ch<'0'||ch>'9') {f|=(ch=='-');ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return;
}
vector<int> p[maxn];
int n,m;
double a[maxn][maxn],ans[maxn],sum;
struct edge
{
int x;
int y;
double E;
}e[maxm];
bool cmp(edge a,edge b)
{
return a.E>b.E;
}
inline void gauss(int m,int n)
{
int i=1,j=1;
while(i<=m&&j<=n)
{
int maxx=i;
for(int k=i+1;k<=m;k++)
if(fabs(a[k][j])>fabs(a[maxx][j]))
maxx=k;
if(fabs(a[maxx][j])>eps)
{
swap(a[maxx],a[i]);
for(int k=i+1;k<=m;k++)
{
double num=a[k][j]/a[i][j];
for(int l=j;l<=n+1;l++)
a[k][l]-=num*a[i][l];
}
i++;
}
j++;
}
for(i=n;i>=1;i--)
{
for(j=i+1;j<=n;j++)
a[i][n+1]-=a[i][j]*ans[j];
ans[i]=a[i][n+1]/a[i][i];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&e[i].x,&e[i].y);
p[e[i].x].push_back(e[i].y);
p[e[i].y].push_back(e[i].x);
}
for(int i=1;i<n;i++)
{
a[i][i]=1.0;
for(int j=0;j<(int)p[i].size();j++)
{
if(p[i][j]==n)
continue;
a[i][p[i][j]]=-(1.0/((double)p[p[i][j]].size()*1.0));
}
if(i==1)
a[i][n]=1.0;
}
gauss(n-1,n-1);
for(int i=1;i<=m;i++)
e[i].E=ans[e[i].x]/(double)(p[e[i].x].size()*1.0)+ans[e[i].y]/(double)(p[e[i].y].size()*1.0);
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++)
sum+=e[i].E*i;
printf("%.3lf",sum);
return 0;
}

