概率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=0∑n(Pj∗dp[i−1]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=1∑6(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]
(1−p1)∗dp[i][j]=2∗(p1+p2+p3)+p2∗dp[i][j+1]+p3∗dp[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+p2∗dp[i][j+1]+p3∗dp[i+1][j])/(1−p1)
当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)+nn−i∗(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]+n−in
没有找到这个题目的链接,但是找到一个类似题目,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[i−1]+n(n−dp[i−1])
同时可以证明:
d
p
[
i
]
=
n
∗
(
1
−
(
n
−
1
n
)
i
)
dp[i]=n*(1-(\frac{n-1}{n})^i)
dp[i]=n∗(1−(nn−1)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+tf∗cntzn=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[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,全部省略了)
注意第三条里面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]=nct∗dp[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]=nn−ct∑(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,?])*?
-
前x秒内,我们最多有min(x,n)个人,所以2)中的?==>1~ min(t,n)
-
转移
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[i−1,j]∗(1−p)+dp[i−1,j−1]∗p -
边界 特判
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[i−1][n]+dp[i−1][n−1]∗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[i−1]+1)+(1−p)∗(E[i]+1)
化简得到:
E
[
i
]
=
1
/
p
+
E
[
i
−
1
]
E[i]=1/p+E[i-1]
E[i]=1/p+E[i−1]
所以
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[i−1]+2∗(E[i−1]+1)−1)+(1−p)∗(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[i−1]+2∗(E[i−1]+1)−1)+(1−p)/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]+(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
(中间的转移代价我放到了外面去,合并成+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)3−x3=x3+3∗x2+3∗x+1−x3=3∗x2+3∗x+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[i−1]+1)∗p+(0+0)∗(1−p)==>f1[i]=(f1[i−1]+1)∗p1−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−x2计算得到的增量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
#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题目推导公式的个人理解,如果有错,希望大家能及时指正。