概率dp 学习笔记

概率dp

总结:
1.

求期望
d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
求概率
d p [ i ] = ∑ ( d p [ 能 转 移 到 i 位 置 的 位 置 ] ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到i位置的位置])*(转移到该位置的概率) dp[i]=(dp[i])()

求期望还有一种最朴素的做法,即权值乘上对应的状态的概率

2.

终止状态确定时候,一般考虑倒推,起始状态确定时候,一般考虑顺推

一般求概率是顺推,求期望很多都是逆推(这些都是不确定的,只是一般性的考虑方向)

求第K次期望的这种题目就一般是正向地推导

例1:麻球繁衍

在这里插入图片描述
dp转移不一定是在后面,也可能是在分叉图的前面(分叉少的部分)就转移

先考虑一只麻球

每次能生N只麻球,每生一只就代表衍生一条边,那么K天之后,分叉数量极大,无法转移

在这里插入图片描述
但是在这个树的根处,分支个数是有限的,<=n,这里是突破口,

dp[i]代表,一开始一只麻球,第i天全部死亡的概率

在这里插入图片描述

所以递推公式如下
d p i = ∑ j = 0 n ( P j ∗ d p [ i − 1 ] j ) dp_i=\sum_{j=0} ^{n}(P_j*dp[i-1]^j) dpi=j=0n(Pjdp[i1]j)
因为K只麻球相互独立,所以最后的答案就是
( d p [ m ] ) k (dp[m])^k (dp[m])k

例2:Problem - 4405 (hdu.edu.cn) Aeroplane chess

(基础的求期望的题目)

题意:每步走等概率走1~6步,求0走到或者超过N位置的期望步数

终止状态确定,从终止状态开始递推**(倒推)**

dp[i]代表从i到n的期望步数

初始化 dp[n]=0,最后答案是dp[0]

dp[i]=sum_(dp[能转移到的位置]+1)/(能转移的位置个数)==> +1(是当前这一步的贡献)

我们对照前面给的公式,转移到的位置应该就是dp[i+j],转移代价是1,概率p是1/6

所以
d p i = ∑ j = 1 6 ( d p i + j + 1 ) / 6 dp_i=\sum_{j=1}^{6}(dp_{i+j}+1)/6 dpi=j=16(dpi+j+1)/6
且能瞬移的点直接dp[i]=dp[to[i]]即可,这个应该不难理解

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N];
double dp[N];
string s,s1,s2;
signed main (){
	while(~scanf("%d%d",&n,&m)){
		if(n==0&&m==0)break;
		map<int,int>mp;
		while(m--){
			cin>>x>>y;
			mp[x]=y;
		}
		rep(i,n,n+10)dp[i]=0;
		dec(i,n-1,0){
			if(mp[i]){
				dp[i]=dp[mp[i]];
				continue;
			}
			dp[i]=0;
			rep(j,1,6){
				dp[i]+=(dp[i+j])*1.0/6.0;
			}
			dp[i]+=1;
		}
		printf("%.4lf\n",dp[0]);
	}
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}


例3 Problem - 3853 (hdu.edu.cn) LOOPS

循环转移的处理:一般都需要照常写出转移方程,然后移项(因为方程两边都有本身,所以需要移项),化简

题意:给一个n*m矩阵,给定每个位置(i,j) 移动到(i,j)的概率p1 ,移动到(i,j+1)的概率p2 ,移动到(i+1,j)的概率p3 .每次移动消耗魔力2

求从(1,1)到(n,m)的期望魔力数

魔力在题目中仅仅影响倍数,没有作用

dp[i,j]代表从(i,j)位置到终点的期望魔力值

终止状态确定,所以考虑逆推


d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
我们同理推出我们的状态转移
d p [ i ] [ j ] = p 1 ∗ ( d p [ i ] [ j ] + 2 ) + p 2 ∗ ( d p [ i ] [ j + 1 ] + 2 ) + p 3 ∗ ( d p [ i + 1 ] [ j ] + 2 ) dp[i][j]=p_1*(dp[i][j]+2)+p_2*(dp[i][j+1]+2)+p_3*(dp[i+1][j]+2) dp[i][j]=p1(dp[i][j]+2)+p2(dp[i][j+1]+2)+p3(dp[i+1][j]+2)
(会产生循环转移,转移到自身的题目,就直接这样列出方程,然后移项化简即可)

==> 移项化简之后得
( 1 − p 1 ) ∗ d p [ i ] [ j ] = 2 ∗ ( p 1 + p 2 + p 3 ) + p 2 ∗ d p [ i ] [ j + 1 ] + p 3 ∗ d p [ i + 1 ] [ j ] (1-p1)*dp[i][j]=2*(p1+p2+p3)+p2*dp[i][j+1]+p3*dp[i+1][j] (1p1)dp[i][j]=2(p1+p2+p3)+p2dp[i][j+1]+p3dp[i+1][j]
==>
d p [ i ] [ j ] = ( 2 + p 2 ∗ d p [ i ] [ j + 1 ] + p 3 ∗ d p [ i + 1 ] [ j ] ) / ( 1 − p 1 ) dp[i][j]=(2+p2*dp[i][j+1]+p3*dp[i+1][j])/(1-p1) dp[i][j]=(2+p2dp[i][j+1]+p3dp[i+1][j])/(1p1)
当p1过小的时候,会导致dp[i,j]特别大,甚至除零,所以需要特判掉,直接使得dp[i,j]等于0(该状态会产生循环,是一种非法状态,或者使得答案大于1000000,也是非法状态)即可

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double dp[N][N],p[4][N][N];
double dfs(int x,int y){
	if(dp[x][y]!=-1){
		return dp[x][y];
	}
	if(x==n&&y==m)return dp[x][y]=0;
	if(x>n||y>m)return dp[x][y]=0;
	if(fabs(1.0-p[1][x][y])<=0.000001)return dp[x][y]=0;
	return dp[x][y]=(2.0+p[2][x][y]*dfs(x,y+1)+p[3][x][y]*dfs(x+1,y))/(1.0-p[1][x][y]);
}
signed main (){
	while(~scanf("%d%d",&n,&m)){
		rep(i,1,n){
			rep(j,1,m){
				rep(k,1,3){
					scanf("%lf",&p[k][i][j]);
				}
			}
		}
		rep(i,0,n+1){
			rep(j,0,m+1){
				dp[i][j]=-1;
			}
		}
		printf("%.3lf\n",dfs(1,1));	
	}
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

例4: 涂格子 系列题目

涂格子1

循环转移+逆推求期望

题意:n个格子,每次随机涂一个,求涂满m个格子的期望次数。

这题基本同例2,终止状态确定,选择逆推

dp[i]代表涂满i个格子的期望次数,dp[m]=0;求dp[0]


d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
得到
d p [ i ] = i n ∗ ( d p [ i ] + 1 ) + n − i n ∗ ( d p [ i + 1 ] + 1 ) dp[i]=\frac{i}{n}*(dp[i]+1)+\frac{n-i}{n}*(dp[i+1]+1) dp[i]=ni(dp[i]+1)+nni(dp[i+1]+1)
i/n和(n-i)/n是几何概型求得的概率,就是面积比等于概率,应该比较好理解

dp[i]代表能转移到本身的位置,即涂在已经涂色的位置,概率是i/n

dp[i]代表,涂在还未涂色的位置,概率是(1-i/n)

化简得到:
d p [ i ] = d p [ i + 1 ] + n n − i dp[i]=dp[i+1]+\frac{n}{n-i} dp[i]=dp[i+1]+nin
没有找到这个题目的链接,但是找到一个类似题目,m改成n即可:

Favorite Dice - SPOJ FAVDICE - Virtual Judge (vjudge.net)

代码如下:

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double dp[N];
signed main (){
	int _t;
	cin>>_t;
	while(_t--){
		int res=0;
		cin>>n;
		dp[n]=0;
		dec(i,n-1,0){
			dp[i]=dp[i+1]+(n*1.0/(n-i));	
		}
		printf("%.2lf\n",dp[0]);
	} 
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

涂格子2 Kids and Prizes - SGU 495 - Virtual Judge (vjudge.net)

题意:n个格子,每次随机涂一个,求涂m次后期望涂色格子数。

这题特殊一点,需要正向处理,终止状态不确定,就是一个比较普通的dp转移

dp[i]代表涂了i次的期望,dp[1]=1,求dp[m]

还是类比逆推的那个公式,来列出方程

在这里插入图片描述

化简之后就得到了
d p [ i ] = d p [ i − 1 ] + ( n − d p [ i − 1 ] ) n dp[i]=dp[i-1]+\frac{(n-dp[i-1])}{n} dp[i]=dp[i1]+n(ndp[i1])

同时可以证明:
d p [ i ] = n ∗ ( 1 − ( n − 1 n ) i ) dp[i]=n*(1-(\frac{n-1}{n})^i) dp[i]=n(1(nn1)i)

应该就是高中的那种求通项公式的方法就行,但我忘得差不多了,以后再补

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5+5;
int n,m;
double dp[N];
int main()
{
    int t;t=1;
    while(t--)
    {
        scanf("%d %d",&n,&m);
        dp[1]=1.0;
        double num=n*1.0;
        for(int i=2;i<=m;i++)
        {
            dp[i]=(dp[i-1]/num)*dp[i-1] + (num-dp[i-1])/num * (dp[i-1]+1);
        }
        printf("%.9f\n",dp[m]);
    }
    return 0;
}

涂格子3

题意:有n个格子,每次会涂一个格子,其中涂第i个格子的概率p[i]。求每个格子都被涂色的期望次数。

需要使用状压来处理

dp[s]==>s是二进制串,代表每个位置是否被涂过色了

求期望,同样考虑逆推,终止状态是dp[2^n-1]=0,求dp[0]

对比
d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
得到转移方程是
d p [ S ] = ∑ ( p i ∗ ( d p [ S o r 2 i ] + 1 ) ) dp[S]=\sum{(p_i*(dp[S {or}2^i]+1)}) dp[S]=(pi(dp[Sor2i]+1))
其中 +1 是转移代价,p[i]是转移概率, dp[S|2^i]是转移的位置

这里会循环转移,所以需要移项处理,处理方法应该随便搞搞都能搞得出来,可以单独把转移到自身的的项数都记录下来

没找到题目来源,但是这个题目和**练习4(在后面)(2021昆明站icpc的B题)**很像,直接看那个题目来理解这个模型即可

例5 A Dangerous Maze - LightOJ 1027 - Virtual Judge (vjudge.net) A Dangerous Maze

几何概型+ 几何分布的期望

几何概型求概率就是面积比

几何分布是在n次伯努利试验中,试验k次才得到第一次成功的机率。详细地说,是:前k-1次皆失败,第k次成功的概率

几何分布的的期望次数是1/p,期望价值在期望次数上乘上每次的平均价值就行!!!!!

(这个期望价值的求法可能不太严谨,应该可以用类似于证明期望次数的方法推导出来,但我并不会推导选择直接记住

题目中

因为是等概率选择一扇门,所以由几何概型可以得知,成功的概率p=(cntz)/n,失败概率q=1-p=(cntf)/n

在这里插入图片描述

这个题目符合几何分布的条件,一直选择门,知道第一次成功为止,所以期望次数是1/p=n/(cntz)

平均价值=(tz+tf)/n

所以,期望价值:
= t z + t f n ∗ n c n t z = t z + t f c n t z =\frac{tz+tf}{n}*\frac{n}{cntz}=\frac{tz+tf}{cntz} =ntz+tfcntzn=cntztz+tf

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N],dp[N];
string s,s1,s2;
signed main (){
	int _t;
	cin>>_t;
	rep(cas,1,_t){
		int res=0;
		cin>>n;
		int cntz=0,cntf=0,tz=0,tf=0;
		rep(i,1,n){
			cin>>a[i];
			if(a[i]>0){
				cntz++;
				tz+=a[i];
			}
			else {
				cntf++;
				tf-=a[i];
			}
		}
		printf("Case %d: ",cas);
		if(cntz==0){
			cout<<"inf\n";
		}
		else {
			int t=tz+tf;
			int all=__gcd(t,cntz);
			printf("%d/%d\n",t/all,cntz/all);
		}
	} 
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

例6 uva11722

几何概型

记录一下这个题目的做法,都说是高中知识的经典例题,但我好像根本没学过这个方法

题意:A在t1-t2时刻等概率到达,B在s1-s2时刻等概率到达,两个人停留时间为w,求相遇概率

在这里插入图片描述

画图求面积比即可,全集是矩形,相遇时间最多差W长,所以画出两条直线即可,中间的面积就是相遇面积。剩下的就是分类讨论的一个几何问题了。

例7 218. 扑克牌 - AcWing题库

前面的求期望的题目都是固定值,这类题目期望可以通过操作的选择,来获取最优期望,即多一个在可行转移状态里面求max/min的一个步骤

题意是给定一套54张的扑克,每次等概率翻出一张牌,不放回,求翻出黑红梅方各各大于A,B,C,D张的期望次数

每次翻出一张大小王,可以自选变成黑红梅方中的一种

dp[a,b,c,d,x,y] 定义为 翻出 四种a,b,c,d ,且大小王状态为x,y 的翻到终止状态的最小期望

大小王0~3表示翻出,且变成四种卡之一,4表示没翻出

先特判终止状态是否合法,只要把每种牌多余13的数量加起来小于等于2就是合法

状态转移:
d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
代价很简单,就是1,且这个代价1是可以移到整个式子外面去的

剩下的牌是rest张,na,nb,nc,nd表示加上大小王的牌数,a,b,c,d,是普通的牌数

概率就是(13-a)/rest,转移的位置就是 dp[a+1,b,c,d,x,y] ,且 需要特判一下,a<13,保证转移到合法状态,其余三种同理

x==4的时候没翻出,表示这里还可以有转移,概率是 1/rest

转移位置是 dp[a,b,c,d,i,y] (0<=i<=3) 这里有可以做最优决策,所以直接用min求一个最小值即可。y同理

细节看代码即可

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2007;
const int mod=1e9+7;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,x,y;
string s,s1,s2;
const double inf =0x3f3f3f3f3f3f3f3f;
double dp[16][16][16][16][5][5];
int vis[16][16][16][16][5][5];
int A,B,C,D;
double dfs(int a,int b,int c,int d,int x,int y){
	double &v=dp[a][b][c][d][x][y];
	if(vis[a][b][c][d][x][y])return v;
	vis[a][b][c][d][x][y]=1;
	int na=a+(x==0)+(y==0);
	int nb=b+(x==1)+(y==1);
	int nc=c+(x==2)+(y==2);
	int nd=d+(x==3)+(y==3);
	if(na>=A&&nb>=B&&nc>=C&&nd>=D){
		return v=0;
	}
	int tot=na+nb+nc+nd;
	int rest=54-tot;
	v=1;
	if(a<13){
		v+=(13-a)*1.0/rest*dfs(a+1,b,c,d,x,y);
	}
	if(b<13){
		v+=(13-b)*1.0/rest*dfs(a,b+1,c,d,x,y);
	}
	if(c<13){
		v+=(13-c)*1.0/rest*dfs(a,b,c+1,d,x,y);
	}
	if(d<13){
		v+=(13-d)*1.0/rest*dfs(a,b,c,d+1,x,y);
	}
	if(x==4){
		double t=inf;
		rep(i,0,3){
			t=min(t,1.0/rest*dfs(a,b,c,d,i,y));
		}
		v+=t;
	}
	if(y==4){
		double t=inf;
		rep(i,0,3){
			t=min(t,1.0/rest*dfs(a,b,c,d,x,i));
		}
		v+=t;
	}
	return v;
}
signed main (){
	cin>>A>>B>>C>>D;
	int duoa=max(A-13,0);
	int duob=max(B-13,0);
	int duoc=max(C-13,0);
	int duod=max(D-13,0);
	if(duoa+duob+duoc+duod<=2){
	    ;
	}
	else {
	    cout<<"-1.000\n";
	    return 0;
	}
	double res=dfs(0,0,0,0,4,4);
	printf("%.3lf",res);
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

例8 P1365 WJMZBMR打osu! / Easy - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

(加强版在练习8里面,建议两题一起看)

题意:给定N长字符串,o表示成功,x表示失败,?表示一半可能成功,一半失败。每一段连续极长的o段,长度为len,贡献len^2的分,求期望得分

令f[i]表示前i个位置的得分**(i位置不一定是o),g[i]表示到第i个位置的已有的连续o的期望长度(i位置一定是o)**

且我们计算f[i]的时候只考虑增加的量即可,即从x2变成(x+1)2的增量 2*x+1

我们还是可以仿照之前的转移模板来写出如下三种转移:
f [ i ] = f [ i − 1 ] ; g [ i ] = 0 ; ( s [ i ] = = ′ x ′ ) f [ i ] = f [ i − 1 ] + 2 ∗ ( g [ i − 1 ] ) + 1 ; g [ i ] = g [ i − 1 ] + 1 ; ( s [ i ] = = ′ o ′ ) f [ i ] = f [ i − 1 ] + ( 2 ∗ ( g [ i − 1 ] ) + 1 ) ∗ p ; g [ i ] = ( g [ i − 1 ] + 1 ) ∗ p + 0 ∗ ( 1 − p ) ; ( s [ i ] = = ′ ? ′ ) ( 此 处 概 率 p 为 1 / 2 , 上 一 题 公 式 也 如 此 , 只 是 p = 1 , 全 部 省 略 了 ) f[i]=f[i-1];g[i]=0; (s[i]=='x') \\ f[i]=f[i-1]+2*(g[i-1])+1;g[i]=g[i-1]+1; (s[i]=='o') \\ f[i]=f[i-1]+(2*(g[i-1])+1)*p;g[i]=(g[i-1]+1)*p+0*(1-p); (s[i]=='?') \\(此处概率p为1/2,上一题公式也如此,只是p=1,全部省略了) f[i]=f[i1];g[i]=0;(s[i]==x)f[i]=f[i1]+2(g[i1])+1;g[i]=g[i1]+1;(s[i]==o)f[i]=f[i1]+(2(g[i1])+1)p;g[i]=(g[i1]+1)p+0(1p);(s[i]==?)(p1/2,p=1)

注意第三条里面g[i]的求法 不是简简单单的 g[i]=g[i-1]+p就行

而是(g[i-1]+1)*p,后面小部分成了零,所以有些题解没写出来,这也是我一直搞错,没理解这个题目的原因

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double f[N],g[N];
signed main (){
	cin>>n>>s;
	s=" "+s;
	double p=0.5;
	rep(i,1,n){
		if(s[i]=='x')f[i]=f[i-1],g[i]=0; 
 		if(s[i]=='o')f[i]=f[i-1]+2*(g[i-1])+1,g[i]=g[i-1]+1;
		if(s[i]=='?')f[i]=f[i-1]+(2*(g[i-1])+1)*p,g[i]=(g[i-1]+1)*p+0*(1-p); 
	}
	printf("%.4lf",f[n]);
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

练习1 Discovering Gold - LightOJ 1030 - Virtual Judge (vjudge.net) lightOJ - 1030

题目基本和例2差不多,套用公式就可以做

题意:一个山洞由n个格子组成 ,每个格子有a[i] 的金子,初始位置在第一个格子,每次扔骰子(1~6)来决定向前的步数,但是如果超过了n就要重新扔,直到走到最后一个格子才算结束。让你求获得金子的期望值。

终止状态确定,选择逆推

套用
d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
转移到的位置就是i+j<=n的位置,转移代价就是a[i],转移到的概率就是 1/cnt (cnt= (i+j<=n)的个数)

详见代码

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double dp[N];
signed main (){
	int _t;
	cin>>_t;
	rep(cas,1,_t){
		int res=0;
		cin>>n;
		rep(i,1,n){
			cin>>a[i];
		}
		dp[n]=a[n];
		dec(i,n-1,1){
			dp[i]=0;
			int cnt=0;
			rep(j,1,6){
				if(i+j<=n)cnt++;
				else break;
				dp[i]+=dp[i+j]+a[i];
			}
			dp[i]=dp[i]*1.0/cnt;
		}
		printf("Case %d: %.10lf\n",cas,dp[1]);	
	} 
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

练习2:217. 绿豆蛙的归宿 - AcWing题库 绿豆蛙的归宿

题意,给一个有向无环图,求起点到终点的期望长度

同练习1,逆推+套用公式即可

见代码

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,p,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
vpii v[N];
double dp[N];
void add(int l,int r,int w){
	v[l].pb({r,w});
}
double dfs(int u){
	if(dp[u]!=-1)return dp[u];
	if(u==n)return 0;
	dp[u]=0;
	for(auto son:v[u])
		dp[u]+=dfs(son.x)+son.y;
	dp[u]=dp[u]*1.0/v[u].size();
	return dp[u];
}
signed main (){
	cin>>n>>m;
	rep(i,1,n)dp[i]=-1;
	rep(i,1,m){
		int l,r,w;
		cin>>l>>r>>w;
		add(l,r,w);
	}
	printf("%.2lf",dfs(1));
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

练习3 uva 11762

数论+期望dp

题意是 给一个N ,一次操作 等概率选择一个小于N的素数,如果是N的约数,就除掉这个数,否则就不动,求N变成1的期望操作数

前面的题目都理解了,那这个题目套用公式应该挺容易推导出递推式子的,懒得写了

!!!练习4 B-Blocks_第46届ICPC亚洲区域赛(昆明)(正式赛) (nowcoder.com)

(当初正赛上,我一看题目以为是个min-max容斥的题目,找到资料搞了老半天,啥也没看懂。那时候对求期望的题目没有概念,只会做概率乘权值的求期望方法,不会逆推求期望

现在学完概率dp,结果就是一个比较模板的题目,怪不得当初这么多人过。正赛93个人过,银牌题水平)

题意:W,H大小的方格本,有N个块,每次等概率随机选择一个涂黑,求全部W*H大小全部涂黑的期望次数

题目和涂方格模型基本相同,就是终止状态不止一个的概率dp,提前预处理一下哪些状态是涂满了的,作为终止状态即可,做法不止一种,我用了离散化加暴力判断。 剩下的转移部分套公式即可

由于用线性状态不好表示,所以采用状压来表示

同例4的涂格子3来处理一下
d p [ S ] = ∑ ( p i ∗ ( d p [ S o r 2 i ] + 1 ) ) dp[S]=\sum{(p_i*(dp[S {or}2^i]+1)}) dp[S]=(pi(dp[Sor2i]+1))
设转移到自身的个数是ct, p[i]在此处=1/n,s’表示转移到非s的状态

移项处理之后:
d p [ S ] = c t n ∗ d p [ S ] + ∑ ( d p [ s ′ ] n ) + 1 dp[S]=\frac{ct}{n}*dp[S]+\sum{(\frac{dp[s']}{n}})+1 dp[S]=nctdp[S]+(ndp[s])+1
==>
d p [ S ] = ∑ ( d p [ s ′ ] n ) + 1 n − c t n dp[S]=\frac{\sum{(\frac{dp[s']}{n}})+1}{\frac{n-ct}{n}} dp[S]=nnct(ndp[s])+1

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2007;
const int mod=998244353;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,x,y;
int w[N],h[N],vis[N],ww[N],hh[N];
int W,H;
int full[N];
int dp[2000];
int a[100][100];
int X,Y;
int check(int x){
	rep(i,0,X+1){
		rep(j,0,Y+1){
			a[i][j]=0;
		}
	}
	rep(k,0,n-1){
		if(x>>k&1){
			rep(i,w[k],ww[k]-1){
				rep(j,h[k],hh[k]-1){
					a[i][j]++;
				}
			}
		}
	}
	rep(i,1,X-1){
		rep(j,1,Y-1){
			if(a[i][j]==0){
				return 0;
			}
		}
	}
	return 1;
}
int dfs(int x){
	if(dp[x]!=-1){
		return dp[x];
	}
	dp[x]=1;
	int ct=0;
	rep(i,0,n-1){
		int to=x|(1ll<<i);
		if(to==x){
			ct++;
			continue;
		}
		dp[x]=(dp[x]+dfs(to)*qmi(n,mod-2)%mod)%mod;
	}
	dp[x]=(dp[x]*n)%mod;
	int chu=n-ct;
	dp[x]=(dp[x]*qmi(chu,mod-2))%mod;
	return dp[x];
}
signed main (){
	
	int _t;
	cin>>_t;
	while(_t--){
		cin>>n;
		vi x,y;
		cin>>W>>H;
		x.pb(W);x.pb(0);
		y.pb(H);y.pb(0);
		X=W;
		Y=H;
		rep(i,0,n-1){
			cin>>w[i]>>h[i]>>ww[i]>>hh[i];
			x.pb(w[i]);x.pb(ww[i]);
			y.pb(h[i]);y.pb(hh[i]);
		}
		sort(all(x));
		x.erase(unique(all(x)),x.end());
		sort(all(y));
		y.erase(unique(all(y)),y.end());
		
		X=x.size();
		Y=y.size();
		
		rep(i,0,n-1){
			w[i]=lower_bound(all(x),w[i])-x.begin()+1;
			h[i]=lower_bound(all(y),h[i])-y.begin()+1;
			ww[i]=lower_bound(all(x),ww[i])-x.begin()+1;
			hh[i]=lower_bound(all(y),hh[i])-y.begin()+1;
		}
		W=X;
		H=Y;
		int cnt=0;
		rep(i,0,(1ll<<n)-1){
			dp[i]=-1;
			if(check(i)){
				dp[i]=0;
				full[i]=1;
				cnt++;
			}
			else full[i]=0;
		}
		if(cnt==0){
			cout<<"-1\n";
			continue;
		}
		cout<<dfs(0)<<"\n";
	}
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

练习5 Problem - 518D - Codeforces

这题一看就是需要二维的dp,因为数据范围用的N方数据

一个维度无法表达状态,主要是因为T和N的大小不确定,当T<=N的时候,就可以用一维来做(要真是这个范围,好像也没有做的必要了,直接P*T即可)

主要是T>N的状态,当电梯塞满了N人之后,接下来概率变了,所以需要二维来表示状态

我们用最朴素的做法,期望=权值乘上对应的状态的概率,来做这个题目

1)dp[i,j]定义为前i秒有j个人在电梯的概率

2)dp[0,0]=1,求sum_(dp[t,?])*?

  1. 前x秒内,我们最多有min(x,n)个人,所以2)中的?==>1~ min(t,n)

  2. 转移
    d p [ i , j ] = d p [ i − 1 , j ] ∗ ( 1 − p ) + d p [ i − 1 , j − 1 ] ∗ p dp[i,j]=dp[i-1,j]*(1-p)+dp[i-1,j-1]*p dp[i,j]=dp[i1,j](1p)+dp[i1,j1]p

  3. 边界 特判
    d p [ i ] [ n ] = d p [ i − 1 ] [ n ] + d p [ i − 1 ] [ n − 1 ] ∗ p dp[i][n] = dp[i-1][n] + dp[i-1][n-1] * p dp[i][n]=dp[i1][n]+dp[i1][n1]p
    前i秒内上了n个人,那么前i-1秒上了n个人是整体转移过来,而不是只转移(1-p)倍,应为已经没人可以再上电梯了,所以概率是1

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double p;
double dp[N][N];
signed main (){
	cin>>n>>p>>t;
	dp[0][0]=1;
	rep(i,1,t){
		dp[i][0]=dp[i-1][0]*(1-p);
		rep(j,1,min(t,n)){
			dp[i][j]=dp[i-1][j]*(1-p)+dp[i-1][j-1]*p;
		}
		dp[i][n] = dp[i-1][n] + dp[i-1][n-1] * p;
	}
	double res=0;
	rep(i,0,min(t,n)){
		res+=i*1.0*dp[t][i];
	}
	printf("%.10lf\n",res);

	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

练习6:King Arthur’s Birthday Celebration POJ - 3682

例5的拓展

题意:投一个硬币,第i次投的代价是2*i-1, 投中正面朝上的概率为给定的p,投到k次正面朝上就停止 。 问投掷次数的期望,期望代价。

我们已知几何分布一次的期望次数是1/p。

题意就是一个几何分布的模型,K次向上是相互独立的,所以我们应该可以猜到期望次数是k/p

当然,也可以证明出来:

设E[i]为i次向上的期望,且p概率向上,那么我们从前面的推导方法可得
E [ i ] = p ∗ ( E [ i − 1 ] + 1 ) + ( 1 − p ) ∗ ( E [ i ] + 1 ) E[i] = p*(E[i-1]+1) + (1-p)*(E[i]+1) E[i]=p(E[i1]+1)+(1p)(E[i]+1)
化简得到:
E [ i ] = 1 / p + E [ i − 1 ] E[i]=1/p+E[i-1] E[i]=1/p+E[i1]
所以
E [ i ] = i / p E[i]=i/p E[i]=i/p
求期望价值:

w[i]设为i次向上的期望价值
w [ i ] = p ∗ ( w [ i − 1 ] + 2 ∗ ( E [ i − 1 ] + 1 ) − 1 ) + ( 1 − p ) ∗ ( w [ i ] + 2 ∗ ( E [ i ] + 1 ) − 1 ) w[i]=p*(w[i-1]+2*(E[i-1]+1)-1) + (1-p)*(w[i]+2*(E[i]+1)-1) w[i]=p(w[i1]+2(E[i1]+1)1)+(1p)(w[i]+2(E[i]+1)1)
由于是求第K次操作的期望,终止状态不明确,所以考虑顺推,感觉好像逆推也不是不行

还是找三个部分,概率,转移位置,转移价值

第一种状态,当前是正面,那么当前状态就从w[i-1]转移过来的,概率是p。且当前是第E[i-1]+1次投掷,所以价值是2*(E[i-1]+1)-1)

第二种状态,当前是反面,那么当前状态就从w[i]转移过来的,概率是1-p。且当前是第E[i]+1次投掷,所以价值是2*(E[i]+1)-1

(并没有理解这里是为什么用这两个价值,网上的题解翻了很多也没有人解释,但感觉只有这两个数是最合理的一种情况,反正我理解的就是当前如果是正面,那么一定是前i-1次数的下一次,所以是E[i]-1,如果是反面,那也就是i次数的下一次,因为第i次是正面,所以不是E[i]次。至于为什么用E[i]来表示次数,我就不清楚了,以后遇到了就这样考虑吧)

化简得到:
w [ i ] = ( w [ i − 1 ] + 2 ∗ ( E [ i − 1 ] + 1 ) − 1 ) + ( 1 − p ) / p ∗ ( 2 ∗ ( E [ i ] + 1 ) − 1 ) w[i]=(w[i-1]+2*(E[i-1]+1)-1) +(1-p)/p * ( 2*(E[i]+1)-1 ) w[i]=(w[i1]+2(E[i1]+1)1)+(1p)/p(2(E[i]+1)1)

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 2e6+7;
double p;
int k;
double w[N];
double E[N];
int main()
{
    while(~scanf("%d",&k) && k)
    {
        scanf("%lf",&p);
        w[0]=0,E[0]=0;
		for(int i=1;i<=k;i++)
        {
            E[i]=1/p+E[i-1];
            w[i]=(w[i-1]+2*(E[i-1]+1)-1) +(1-p)/p * ( 2*(E[i]+1)-1 );
        }
        printf("%.3f %.3f\n",E[k],w[k]);
    }
    return 0;
}

这题用%lf输出就wa,看了别人的用了%f就AC,很奇怪

练习7 Collecting Bugs POJ - 2096

题意:一共有s个系统 ,共有n种bug, 每天可以找到一个bug(发生在每个系统的概率为1/s, 每种bug出现的概率为1/n)。 问每个系统都至少找到1个bug 且每种bug都被发现的期望天数 。

终止状态确定,选择逆推

很容易想到状态定义dp[i,j] 表示i个系统发现j个bug的期望 dp[n,s]=0, 求dp[0,0]

转移应该有四种,分别是是否是个新bug,是否发生在已经有bug的系统,总共四种
d p [ i , j ] = ( i / s ) ∗ ( j / n ) ∗ d p [ i , j ] + ( s − i ) / s ∗ j / n ∗ d p [ i + 1 ] [ j ] + ( n − j ) / j ∗ i / s ∗ d p [ i , j + 1 ] + ( s − i ) / s ∗ ( n − j ) / n ∗ d p [ i + 1 , j + 1 ] + 1 dp[i,j]=(i/s)*(j/n)*dp[i,j]+(s-i)/s*j/n*dp[i+1][j] +(n-j)/j*i/s*dp[i,j+1]+(s-i)/s*(n-j)/n *dp[i+1,j+1]+1 dp[i,j]=(i/s)(j/n)dp[i,j]+(si)/sj/ndp[i+1][j]+(nj)/ji/sdp[i,j+1]+(si)/s(nj)/ndp[i+1,j+1]+1
(中间的转移代价我放到了外面去,合并成+1加在了外面)

第一部分是表示 不是在新系统,且不是新bug的情况,被转移的状态是dp[i,j],概率p是因为两者是独立关系,所以求出两个概率相乘即可,每种概率就是一个简单的几何概型,求一下占比即可。

其余三部分同理

练习8 P1654 OSU! - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这题又和前面的不太一样了,顺推求期望,感觉又不是同一类题型了。

(这是一道一看就会,一做就废的典型题目),这题看了很久,没看懂网上的题解,很多人只是放了公式,没有解释,于是找了个类似的题目,弱化版的放在了例8,那个题目挺容易看懂的,这个题目就好理解一点了。

题意:开始有一个空串,每次添加一个0或1,添加1的概率为p。添加完后计算得分,每一段连续极长的连续1段贡献len^3分。求最后期望得分。

这题着手点在len^3,和一些平方的题目一样,考虑展开拆项,分别维护期望值

每次长度从x变成(x+1)那么贡献变化= (x+1)3-x3

展开得:
( x + 1 ) 3 − x 3 = x 3 + 3 ∗ x 2 + 3 ∗ x + 1 − x 3 = 3 ∗ x 2 + 3 ∗ x + 1 (x+1)^3-x^3=x^3+3*x^2+3*x+1-x^3=3*x^2+3*x+1 (x+1)3x3=x3+3x2+3x+1x3=3x2+3x+1
所以我们每次贡献变化就需要维护除x^2和x的期望贡献即可

我们对比例8的题目,我们先考虑用两个辅助数组,f1[]和f2[]来表示到i位置,且i已有的连续1的x的期望和x^2的期望**(这里两个数组要的是要求i位置是1的状态的)**,最后的答案数组直接f3[]直接可以通过这两个数组来计算贡献

f3[]表示的是前i个位置的x^3的期望贡献**(这里是不要求i位置是1的)**, f3[i]=f3[i-1]+(增量3x^2+3x+1),这里的增量计算,就是需要从i-1是1转移到i是1之间的差值来求出来的增量,所以需要上面两个数组的不同定义

最后输出f3[n]即可

我们可以利用前面的递推方法,
d p [ i ] = ∑ ( d p [ 能 转 移 到 的 位 置 ] + 转 移 代 价 ) ∗ ( 转 移 到 该 位 置 的 概 率 ) dp[i]=\sum(dp[能转移到的位置]+转移代价)*(转移到该位置的概率) dp[i]=(dp[]+)()
来试着推一下公式
f 1 [ i ] = ( f 1 [ i − 1 ] + 1 ) ∗ p + ( 0 + 0 ) ∗ ( 1 − p ) = = > f 1 [ i ] = ( f 1 [ i − 1 ] + 1 ) ∗ p 1 − p 的 概 率 出 一 个 0 , 按 照 f 1 的 定 义 , 那 转 移 到 的 位 置 就 是 一 个 期 望 是 0 的 位 置 f 2 [ i ] = ( f 2 [ i − 1 ] + ( 2 ∗ f 1 [ i − 1 ] + 1 ) ) ∗ p + ( 0 + 0 ) ∗ ( 1 − p ) = = > f 2 [ i ] = ( f 2 [ i − 1 ] + ( 2 ∗ f 1 [ i − 1 ] + 1 ) ) ∗ p 这 里 的 权 值 计 算 是 用 ( x + 1 ) 2 − x 2 计 算 得 到 的 增 量 f 3 [ i ] = ( f 3 [ i − 1 ] + ( 3 ∗ f 2 [ i − 1 ] + 3 ∗ f 1 [ i − 1 ] + 1 ) ) ∗ p + ( f 3 [ i − 1 ] + 0 ) ∗ ( 1 − p ) = = > f 3 [ i ] = f 3 [ i − 1 ] + ( 3 ∗ f 2 [ i − 1 ] + 3 ∗ f 1 [ i − 1 ] + 1 ) ∗ p f1[i]=(f1[i-1]+1)*p+(0+0)*(1-p)==> f1[i]=(f1[i-1]+1)*p \\1-p的概率出一个0,按照f1的定义,那转移到的位置就是一个期望是0的位置 \\ f2[i]=(f2[i-1]+(2*f1[i-1]+1))*p+(0+0)*(1-p)==>f2[i]=(f2[i-1]+(2*f1[i-1]+1))*p \\这里的权值计算是用(x+1)^2-x^2计算得到的增量 \\ f3[i]=(f3[i-1]+(3*f2[i-1]+3*f1[i-1]+1))*p+(f3[i-1]+0)*(1-p) \\ ==> f3[i]=f3[i-1]+(3*f2[i-1]+3*f1[i-1]+1)*p f1[i]=(f1[i1]+1)p+(0+0)(1p)==>f1[i]=(f1[i1]+1)p1p0f10f2[i]=(f2[i1]+(2f1[i1]+1))p+(0+0)(1p)==>f2[i]=(f2[i1]+(2f1[i1]+1))p(x+1)2x2f3[i]=(f3[i1]+(3f2[i1]+3f1[i1]+1))p+(f3[i1]+0)(1p)==>f3[i]=f3[i1]+(3f2[i1]+3f1[i1]+1)p

#include<bits/stdc++.h>
using namespace std; 
typedef long long ll; 
#define int long long 
#define ull unsigned long long
#define CY printf("YES\n")
#define CN printf("NO\n")
#define x first
#define y second
#define PII pair<int,int > 
#define de(x) cerr<<#x<<'='<<x<<'\n'
#define dde(x,y) cerr<<#x<<'='<<x<<","<<#y<<"="<<y<<'\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define dec(i, a, b) for(int i = (a); i >= (b); i --)
#define vi vector<int>
#define vpii vector<PII>
#define pb push_back
#define rvs(s) reverse(s.begin(),s.end())
#define all(s) s.begin(),s.end()
int qmi(int a, int k);
int mo(int x,int p){return x = ((x%p)+p)%p;}
const int N=2000007;
const int mod=1e9+7;
const int inf=0x3f3f3f3f3f3f3f3f;
const double eps=1e-8;
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
int  n,m,k,t,q,x,y;
int a[N],vis[N],b[N];
string s,s1,s2;
double f1[N],f2[N],f3[N],p[N];
signed main (){
	cin>>n;
	rep(i,1,n){
		cin>>p[i];
		f1[i]=(f1[i-1]+1)*p[i];
		f2[i]=(f2[i-1]+(2*f1[i-1]+1))*p[i];
		f3[i]=f3[i-1]+(3*f2[i-1]+3*f1[i-1]+1)*p[i];
	}
	printf("%.1lf",f3[n]);
	return 0;
}
int qmi(int a, int k){int res = 1;while (k){if (k & 1) res = (ll)res * a % mod;a = (ll)a * a % mod;k >>= 1;}return res;}

后记:
概率dp应该算是入门了,但是难的题目还是没法做,这些题目里面基本都是线性求的期望,还有一些树上求期望的题目,树上随机游走的一些题目需要学习。这里面好多都还需要很多的数学知识,然而我的数学知识储备太少,留着以后再学了。
这篇文章我也只是做了概率dp一些经典题目的整理,还有一些我对概率dp题目推导公式的个人理解,如果有错,希望大家能及时指正。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一篇关于区间DP学习笔记,希望对你有所帮助。 ### 什么是区间DP 区间 DP 是一种动态规划算法,用于解决一些区间上的问题。具体来说,区间 DP 通常用于解决如下问题: - 最长公共子序列(LCS) - 最长递增子序列(LIS) - 最大子段和 - 区间选数问题 区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。 ### 区间 DP 的递推方法 区间 DP 的递推方法通常有两种,一种是自底向上的递推方法,一种是自顶向下的记忆化搜索方法。 自底向上的递推方法通常采用二维数组或三维数组来记录状态转移方程,具体的递推方式如下: ```cpp for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; for (int k = i; k < j; k++) { // 状态转移方程 } } } ``` 其中,len 表示区间长度,i 和 j 分别表示区间的左右端点,k 表示区间的划分点。 自顶向下的记忆化搜索方法通常采用记忆化数组来记录状态转移方程,具体的递推方式如下: ```cpp int dp(int i, int j) { if (i == j) return 0; if (memo[i][j] != -1) return memo[i][j]; memo[i][j] = INF; for (int k = i; k < j; k++) { memo[i][j] = min(memo[i][j], dp(i, k) + dp(k + 1, j) + ...); } return memo[i][j]; } ``` 其中,i 和 j 分别表示区间的左右端点,k 表示区间的划分点,memo 数组用于记忆化状态转移方程。 ### 区间 DP 的优化 对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。 一种常见的优化方式是状态压缩,将二维或三维数组压缩成一维数组,从而减少空间的消耗。 另一种常见的优化方式是使用滚动数组,将数组的维度从二维或三维减少到一维,从而减少时间和空间的消耗。 此外,对于一些具有特殊性质的区间 DP 问题,我们还可以使用单调队列或单调栈等数据结构来进行优化,从而减少时间和空间的消耗。 ### 总结 区间 DP 是一种常用的动态规划算法,用于解决一些区间上的问题。区间 DP 通常采用分治或递推的方式进行求解,具体方法取决于问题的性质。对于一些区间 DP 问题,我们可以通过一些技巧和优化来减少时间和空间的消耗。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值