笔记——概率期望DP·Part1
笔记——概率期望DP·Part1
概率#
概率是反映随机事件出现的可能性大小的量度。
一般用
表示事件, 为 发生的概率。
性质#
-
对于任意一个事件
: -
有限可加性:当
个事件 两两互不相容时: 。
是并,在这里相当于逻辑或。
- 当事件
满足 包含于 时:
就是从 中剔除 ,可以理解为 发生而 不发生。
- 对任意两个事件
和 ,
这条相当于上一条,可以把
视为一个整体。
可以从上一条推出来。
条件概率#
条件概率是指事件
注意:
与 不同。
是交,这里可以理解为逻辑与。
统计独立性#
当且仅当两个随机事件
同样,对于两个独立事件
换句话说,如果
互斥性#
当且仅当
因此,
换句话说,如果
期望#
数学期望(mean)(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和,是最基本的数学特征之一。它反映随机变量平均取值的大小。
设
对应的概率为
则期望为
性质#
, 为常数。
相当于在合式里面乘了一个数
,将 提出来。
-
, 和 是任意两个随机变量 -
, 和 是相互独立的随机变量
题目#
了解了这些基础的东西之后,就该做题了吧!
P1654 OSU!#
题意#
你有一个长度为
这个串的第
定义这个串的分数为:这个串中连续的
问这个串的期望分数。
做法#
先考虑一个简单的东西。
设
容易发现,
那就是说
那么我们就可以得到
然后我们设
那么
为什么呢?
首先,显然地,一定会继承前
然后,有
显然,应该加上
那么这一部分就是:
那么上面的式子就显然了。
代码#
稍微改了下变量名。
int main(){
n=read();
for(int i=1;i<=n;i++)cin>>p[i];
for(int i=1;i<=n;i++){
x[i]=(x[i-1]+1)*p[i];
x2[i]=(x2[i-1]+2*x[i-1]+1)*p[i];
ans[i]=ans[i-1]+(3*x2[i-1]+3*x[i-1]+1)*p[i];
}
cout<<fixed<<setprecision(1)<<ans[n];
return 0;
}
P4550 收集邮票#
题意#
皮皮想要收集
做法#
设
容易发现,
那么不妨先求一个简单的东西:买到已经有了
显然,
想当然地,当我们求出了需要的次数时,我们就能求出需要的钱数。
钱数为
但这样是过不了样例的。可以类比在第一题中不能直接用
虽然不能直接求出答案,但是我们解决了求出已经购买的次数的问题。
如果我们从后往前计数,就是说,最后一次购买的花费是
所以,
容易发现,这里的
这是不方便处理的,所以我们对这个式子进行化简。
然后直接转移即可。
代码#
int main(){
n=read();
for(int i=n-1;~i;i--)f[i]=f[i+1]+1.0*n/(n-i);
for(int i=n-1;~i;i--)dp[i]=1.0*i/(n-i)*(f[i]+1)+dp[i+1]+f[i+1]+1;
cout<<fixed<<setprecision(2)<<dp[0];
return 0;
}
P3802 小魔女帕琪#
题意#
帕琪拥有
等概率选取就是说:如果有两个
做法#
考虑前
其中,
因为当固定顺序时,概率为:
又因为有
容易发现,每一次能够释放七重奏的概率相等。
比方说,求出第
可以考虑枚举第一位选择的魔法,然后计算,发现和第一次就释放七重奏的概率相等。
所以,最后的答案就是:
代码#
稍微改了一下变量名。
有毒瘤数据,其中
int main(){
for(int i=1;i<=n;i++)sum+=a[i]=read();
for(int i=1;i<=n;i++){
if(i<7)ans*=1.0*a[i]/(sum-i+1);
else ans*=1.0*a[i];
}
ans*=5040;//7的阶乘等于5040
cout<<fixed<<setprecision(3)<<ans;
return 0;
}
P6858 深海少女与胖头鱼#
题意#
题目大意:
有
啊对了,这道题
做法#
显然,状态里面要记录有护盾和没有护盾的敌人数量。就是说,
如果打到了护盾上,那么会变成
如果没有打到护盾上,那么会变成
那么就有了转移式子:
这样的复杂度为
如果能快速地求出
容易发现:
观察题解可得, 这个式子化简之后就是:
或者,用矩阵加速递推算这个东西也是很快的。
将这个式子带入之后,就得到了一个
因为需要求逆元,所以最终复杂度为
代码#
因为
const int mod=998244353;
int n,m;
int ny(int x){//快速幂求逆元
int ans=1;
for(int b=mod-2;b;b>>=1){
if(b&1)ans=(ans*x)%mod;
x=(x*x)%mod;
}
return ans;
}
int g(int x){//快速求出j=1的值
return ((((x*x)%mod+(5*x)%mod+2)%mod)*ny(2))%mod;
}
int f(int a,int b){
if(!b)return (g(a-1)+1)%mod;
if(b==1)return g(a);
return (((a*ny(a+b)%mod)*g(a+b-1))%mod+((b*ny(a+b)%mod)*f(a,b-1))%mod+1)%mod;
}
signed main(){
n=read()%mod,m=read()%mod;
cout<<f(n,m);
return 0;
}
P1850 [NOIP2016 提高组] 换教室#
题意#
题目大意:有
你可以提交申请以更换教室。一开始,第
你至多可以提交
做法#
首先,跑一遍Floyd,求出点与点之间的最短路。
设
考虑转移。
如果一节课没有提交申请,转移时有两种选择:上节课交了申请和没有交申请。枚举所有情况取最小值即可。
如果一节课交了申请,转移时有两种选择:上节课交了申请和没有交申请。枚举所有情况取最小值即可。
具体的式子太长了,见代码。
代码#
const int maxn=2010;
int n,m,v,e;
int dis[maxn][maxn];
int c[maxn],d[maxn];
double k[maxn];
double dp[maxn][maxn][2];
double ans=1e9;
void init(){//初始化dp数组为一个极大值
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
dp[i][j][0]=dp[i][j][1]=1e9;
}
}
dp[1][0][0]=dp[1][1][1]=0;//上完第一节课走的路程为0
}
signed main(){
n=read(),m=read(),v=read(),e=read();
init();
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=n;i++)d[i]=read();
for(int i=1;i<=n;i++)scanf("%lf",&k[i]);
for(int i=1;i<=e;i++){
int a=read(),b=read(),c=read();
dis[a][b]=dis[b][a]=min(dis[a][b],c);
}
Floyd();//求最短路
for(int i=2;i<=n;i++){//计算dp值
for(int j=0;j<=min(m,i);j++){
dp[i][j][0]=min(dp[i-1][j][0]+dis[c[i-1]][c[i]],dp[i-1][j][1]+k[i-1]*dis[d[i-1]][c[i]]+(1.0-k[i-1])*dis[c[i-1]][c[i]]);//这节课没交申请,一个是定值,另一个枚举两种情况
if(j)dp[i][j][1]=min(dp[i-1][j-1][0]+k[i]*dis[c[i-1]][d[i]]+(1.0-k[i])*dis[c[i-1]][c[i]],dp[i-1][j-1][1]+k[i]*k[i-1]*dis[d[i-1]][d[i]]+k[i]*(1.0-k[i-1])*dis[c[i-1]][d[i]]+(1.0-k[i])*k[i-1]*dis[d[i-1]][c[i]]+(1.0-k[i])*(1.0-k[i-1])*dis[c[i-1]][c[i]]);//这节课交了申请,枚举两种情况和枚举四种情况
}
}
for(int i=0;i<=min(n,m);i++){//得到答案
ans=min(ans,min(dp[n][i][0],dp[n][i][1]));
}
cout<<fixed<<setprecision(2)<<ans;
return 0;
}
P4316 绿豆蛙的归宿#
题意#
给出张
绿豆蛙从起点出发,走向终点。 到达每一个顶点时,如果该节点有
做法#
先考虑设
那么转移就要考虑这个点从前面某个点转移来的概率。
这并不好求。
考虑反着定义状态。设
那么
其中,
方便起见,使用刷表法。
代码#
void topsort(){//拓扑排序
queue<int>q;
q.push(n);
while(!q.empty()){
int now=q.front();q.pop();
for(edge nxt:g[now]){
--in[nxt.to];
dp[nxt.to]+=1.0*(dp[now]+nxt.cost)/out[nxt.to];//刷表法
if(!in[nxt.to])q.push(nxt.to);
}
}
}
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
add(u,v,w);
}
topsort();
cout<<fixed<<setprecision(2)<<dp[1];
return 0;
}
P6154 游走#
题意#
B 城可以看作一个有
zbw 在 B 城随机游走,他会在所有路径中随机选择一条路径,选择所有路径的概率相等。路径的起点和终点可以相同。
定义一条路径的长度为经过的边数,你需要求出 zbw 走的路径长度的期望,答案对
做法#
因为选择每一条路径的概率相等,所以考虑求出路径总数和所有路径长度的和。
因为每个点都可以作为起点出现,所以考虑以每一个点为起点,求一遍路径总长和路径总数,然后再加起来。
显然这样的复杂度为
考虑加入一个超级源点
显然,这时原图路径的总数等于从超级源点到超级汇点的路径总数。
那路径总长度等于从超级源点到超级汇点的路径长度总和吗?
容易发现,每条路径都会经过
所以,每条路径的长度都多了
代码#
void topsort(){
queue<int>q;
cnt[s]=1;
sum[s]=0;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
for(int nxt:g[now]){
--in[nxt];
cnt[nxt]=(cnt[nxt]+cnt[now])%mod;
sum[nxt]=(sum[nxt]+sum[now]+cnt[now])%mod;
if(!in[nxt])q.push(nxt);
}
}
}
int ksm(int x){
int ans=1;
for(int b=mod-2;b;b>>=1){
if(b&1)ans=(ans*x)%mod;
x=(x*x)%mod;
}
return ans;
}
signed main(){
n=read(),m=read();
s=0,t=n+1;
for(int i=1;i<=m;i++){
int u=read(),v=read();
g[u].push_back(v);
++in[v];
}
for(int i=1;i<=n;i++){
g[s].push_back(i);
g[i].push_back(t);
++in[i],++in[t];
}
topsort();
sum[t]=(((sum[t]-(cnt[t]<<1))%mod)+mod)%mod;
cout<<(sum[t]*ksm(cnt[t]))%mod;
return 0;
}
P5104 红包发红包#
题意#
有一个抢红包的系统,红包总金额为
如果红包里还剩
求第
做法#
设
根据题意,
考虑求出其他的
不妨设
显然
使用快速幂求出即可。
代码#
int ksm(int a,int b){
int ans=1;
for(;b;b>>=1){
if(b&1)ans=(ans*a)%mod;
a=(a*a)%mod;
}
return ans;
}
signed main(){
w=read()%mod,n=read(),k=read();
ans=w;
ans=(ans*ksm(ksm(2,k),mod-2))%mod;
cout<<ans;
return 0;
}
P1297 [国家集训队]单选错位#
题意#
给定了一组数
你做对了所有题,但你抄答案时错位了。具体来说,第
做法#
容易发现每种情况的概率是相等的,考虑求出所有可能情况数和所有可能情况下做对的题目的总数。
显然,总方案数
先不着急,往下推式子先。
考虑第
考虑这两道题,发现总共有
那么第
综上,总贡献就是:
这样,溢出的问题就解决了,直接计算即可。
代码#
for(int i=1;i<n;i++){
ans+=1.0/max(a[i],a[i+1]);
}
ans+=1.0/max(a[1],a[n]);
P1365 WJMZBMR打osu! / Easy#
题意#
有
现在给定一个字符串,表示 WJMZBMR 在游戏中击打的结果,其中 "?" 号可以代表 "o" 或 "x",各自概率为
做法#
类比第一题。
将o
视为有 x
为有 ?
视为有
和第一题的区别仅仅是从
于是设
那么
直接转移即可,答案为
代码#
signed main(){
n=read();
scanf("%s",s+1);
for(int i=1;i<=n;i++){
if(s[i]=='o')p[i]=1;
if(s[i]=='x')p[i]=0;
if(s[i]=='?')p[i]=0.5;
}
for(int i=1;i<=n;i++){
f[i]=p[i]*(f[i-1]+1);
dp[i]=dp[i-1]+p[i]*(2*f[i-1]+1);
}
cout<<fixed<<setprecision(4)<<dp[n];
return 0;
}
CF16E Fish#
题意#
有
若两条标号为
这样的过程会一直进行下去,直到湖中只剩下一条鱼。请你计算每条鱼最后留在湖中的概率。
做法#
观察数据范围,考虑状压dp。
设
考虑转移。枚举上一次被吃掉的鱼和吃掉它的鱼。那么转移就是:
按照式子转移即可。
代码#
signed main(){
n=read();
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
scanf("%lf",&a[i][j]);
}
}
dp[(1<<n)-1]=1;
for(int i=(1<<n)-2;i;--i){
int siz=__builtin_popcount(i);
for(int lst=0;lst<n;++lst){//上一次被吃掉的
if((1<<lst)&(i))continue;//被吃了的一定死了
for(int now=0;now<n;++now){//吃了他的
if(!((1<<now)&(i)))continue;//吃了他的一定还活着
if(now==lst)continue;//自己不会吃自己
int j=i^(1<<lst);
dp[i]+=dp[j]*a[now][lst]/(siz*(siz+1)/2);
}
}
}
for(int i=1;i<(1<<n);i++){
if(__builtin_popcount(i)==1)
cout<<fixed<<setprecision(6)<<dp[i]<<' ';//偷懒的写法
}
return 0;
}
P6046 纯粹容器#
题意#
有
下面会进行进行
求每个容器存活轮数的期望,未被击碎的轮数。
做法#
观察题解,得到一个美妙的公式:
也就是说,我们不用去求一个容器恰好在第
容易发现,对于一个容器
那么不妨计左边第一个比它大的为
那么我们要算出
计算可得:
按照上面的式子计算即可。
代码#
//c(a,b)表示a个中选b个
//inv(a)表示a的逆元
void init(){
fact[0]=1;
for(int i=1;i<=n;i++)fact[i]=(fact[i-1]*i)%mod;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int j=i-1;j>=1;--j){//左边第一个比它大的
if(a[j]>a[i]){
ld[i]=i-j;
break;
}
}
for(int j=i+1;j<=n;++j){//右边第一个比它大的
if(a[j]>a[i]){
rd[i]=j-i;
break;
}
}
}
}
}
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
init();
for(int i=1;i<=n;i++){
int ans=0;
for(int j=1;j<n;j++){
int pa=0,pb=0,pab=0;
if(ld[i]&&j-ld[i]>=0){//判断是否存在
pa=c(n-1-ld[i],j-ld[i])*inv(c(n-1,j))%mod;
}
if(rd[i]&&j-rd[i]>=0){//判断是否存在
pb=c(n-1-rd[i],j-rd[i])*inv(c(n-1,j))%mod;
}
if(ld[i]&&rd[i]&&j-ld[i]-rd[i]>=0){//判断是否存在
pab=c(n-1-ld[i]-rd[i],j-ld[i]-rd[i])*inv(c(n-1,j))%mod;
}
ans=(ans+1-pa-pb+(mod<<1)+pab)%mod;
}
cout<<ans<<' ';
}
return 0;
}
CF1042E Vasya and Magic Matrix#
题意#
一个
给定一个出发点,每次可以等概率的移动到一个权值小于当前点权值的点,同时得分加上两个点之间欧几里得距离的平方(欧几里得距离:
做法#
首先,按照
设
其中,
考虑如何快速转移。显然需要的
考虑如何处理每次转移的分数。容易发现(以
然后直接用前缀和求出即可。
代码#
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[++node].val=read()+1;
a[node].x=i;
a[node].y=j;
}
}
sx=read(),sy=read();
sort(a+1,a+1+node,cmp);
for(int i=1;i<=node;i++){
x[i]=(x[i-1]+a[i].x)%mod;
x2[i]=(x2[i-1]+a[i].x*a[i].x)%mod;
y[i]=(y[i-1]+a[i].y)%mod;
y2[i]=(y2[i-1]+a[i].y*a[i].y)%mod;
}
for(int i=1;i<=node;i++){
if(a[lst].val!=a[i].val)lst=i;
int xx=((lst-1)*(a[i].x*a[i].x)%mod-2*(a[i].x*x[lst-1])%mod+x2[lst-1])%mod;
int yy=((lst-1)*(a[i].y*a[i].y)%mod-2*(a[i].y*y[lst-1])%mod+y2[lst-1])%mod;
dp[i]=(sum[lst-1]+xx+yy)*inv(lst-1)%mod;
sum[i]=(sum[i-1]+dp[i])%mod;
if(a[i].x==sx&&a[i].y==sy){
cout<<dp[i];
break;
}
}
return 0;
}
CF453A Little Pony and Expected Maximum#
题意#
有一个
做法#
考虑求出掷出
掷
掷
掷
那么掷
所以最大值为
所以最大值的期望就是:
不难发现,这样肥肠容易溢出。所以考虑对这个式子进行化简:
直接计算即可。
代码#
signed main(){
m=read(),n=read();
for(int i=1;i<=m;i++){
ans+=1.0*i*(ksm(1.0*i/m,n)-ksm(1.0*(i-1)/m,n));
}
cout<<fixed<<setprecision(5)<<ans;//保留5位小数即可
return 0;
}
呃#
这篇博客有点太长了,就先从这断开了。后面大概会写个Part2。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现