前言
此文的题目均来源于luogu 期望标签下评级不高于蓝题(提高左右)的题目。
题目难度递增,前置很少,需要学会前缀和,DP,期望的基本定义,了解期望的线性性质即可。
性质并非笔记,类似做题记录或者说题解集合。
注释
本文中的期望都指数学期望。
- 定义:可能结果乘以概率的总和。比如投掷色子,结果为 1∼5,概率均为 1/6,期望就是 1×16+…+6×16
- 线性性质:E(a+b)=E(a)+E(b) ,其中 E 代表期望,a,b 分别是两个事件。
P6154 游走
题意
给定一个有向无环图,求其中一条路径长度的期望。
思路
对于一条路径,E=sumcnt ,sum 为长度总和, cnt 是路径个数。
考虑记忆化搜索。令 fi 表示 i 开始的路径长度和, gi 表示路径条数。
那么 fi=∑fj+gj,gi=1+∑gj (加上自己到自己)
ans=∑fi∑gi
代码
常数优化代码:
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
return x*f;
}
int fmod( int x ) { x-=mod,x+=x>>31&mod; return x; }
不加优化的代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10,M=7e5+10;
const ll mod=998244353;
struct edge
{
int to,nxt;
}e[M];
int tot,head[N],n,m;
ll f[N],g[N];
void add( int u,int v )
{
e[++tot]=(edge){v,head[u]}; head[u]=tot;
}
ll power( ll a,ll b,ll P )
{
ll res=1;
for ( ; b; b>>=1,a=a*a%P )
if ( b&1 ) res=res*a%P;
return res;
}
void dp( int u )
{
if ( g[u] ) return;
g[u]=1;
for ( int i=head[u]; i; i=e[i].nxt )
{
int v=e[i].to; dp( v );
g[u]=(g[u]+g[v])%mod;
f[u]=(f[u]+f[v]+g[v])%mod;
}
}
int main()
{
scanf( "%d%d",&n,&m );
for ( int i=1,u,v; i<=m; i++ )
scanf( "%d%d",&u,&v ),add( u,v );
memset( g,0,sizeof(g) ); memset( f,0,sizeof(f) );
for ( int i=1; i<=n; i++ )
if ( !g[i] ) dp(i);
ll s1=0,s2=0;
for ( int i=1; i<=n; i++ )
s1=(s1+f[i])%mod,s2=(s2+g[i])%mod;
printf( "%lld\n",s1*power(s2,mod-2,mod)%mod );
}
P1850 换教室
题意
有 n 节课程,如果申请通过则在 di 上课,否则在 ci,第 i 节课的通过概率是 ki。每个人至多选择 m 门课程申请。
学校为 v点e边的无向图,每条路有消耗的体力值,每次下课就选择最短路前往下一个教室。求耗费体力值的总和的最小期望值。
思路
令 dp[i][j][0/1] 表示前 i 堂课申请了 j 次,第 i 堂课申请或不申请的最小期望花费。分类讨论。
-
如果第 i 堂课不申请:
-
i-1 也不申请,那么期望花费是: t1=f[c[i−1]][c[i]]
-
申请,t2=k[i−1]×f[d[i−1]][c[i]]+(1−k[i−1])×f[c[i−1]][c[i]]
结合起来就是:dp[i][j][0]=min(dp[i−1][j][0]+t1,dp[i−1][j][1]+t2)
-
第 i 堂课申请:
-
i-1不申请 t1=k[i]×f[c[i−1]][d[i]]+(1−k[i])×f[c[i−1]][c[i]]
-
i-1也申请
-
都成功:t2=k[i−1]×k[i]×f[d[i−1]][d[i]]
-
i-1成功:t3=k[i−1]×(1−k[i])×f[d[i−1]][c[i]]
-
i 成功:t4=(1−k[i−1])×k[i]×f[c[i−1]][d[i]]
-
没成功:t5=(1−k[i−1])×(1−k[i])×f[c[i−1]][c[i]]
-
总结一下就是:dp[i][j][1]=min(dp[i−1][j−1][0]+t1,dp[i−1][j−1][1]+t2+t3+t4+t5
边界:dp[1][0][0]=0,dp[1][1][1]=0
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2010,V=310;
const double inf=1e9+7;
double p[N],dis[V][V],f[N][N][2];
int c[N],d[N],n,m,v,e;
void get_dis()
{
for ( int k=1; k<=v; k++ )
for ( int i=1; i<=v; i++ )
for ( int j=1; j<=v; j++ )
dis[i][j]=dis[j][i]=min( dis[i][j],dis[i][k]+dis[k][j] );
}
void init()
{
for ( int i=1; i<=v; i++ )
{
for ( int j=1; j<i; j++ )
dis[i][j]=dis[j][i]=inf;
dis[i][i]=0; dis[0][i]=0;
}
for ( int i=1; i<=n; i++ )
for ( int j=0; j<=m; j++ )
f[i][j][0]=f[i][j][1]=inf;
f[1][0][0]=f[1][1][1]=0;
}
int main()
{
scanf( "%d%d%d%d",&n,&m,&v,&e );
for ( int i=1; i<=n; i++ )
scanf( "%d",&c[i] );
for ( int i=1; i<=n; i++ )
scanf( "%d",&d[i] );
for ( int i=1; i<=n; i++ )
scanf( "%lf",&p[i] );
init();
for ( int i=1,u,v,w; i<=e; i++ )
scanf( "%d%d%d",&u,&v,&w ),dis[u][v]=dis[v][u]=min( dis[u][v],(double)w );
get_dis();
for ( int i=2; i<=n; i++ )
for ( int j=0; j<=min(i,m); j++ )
{
double t1=dis[c[i-1]][c[i]],t2=p[i-1]*dis[d[i-1]][c[i]]+(1-p[i-1])*dis[c[i-1]][c[i]];
f[i][j][0]=min( f[i-1][j][0]+t1,f[i-1][j][1]+t2 );
if ( j>0 )
{
t1=p[i]*dis[c[i-1]][d[i]]+(1-p[i])*dis[c[i-1]][c[i]];
t2=p[i-1]*p[i]*dis[d[i-1]][d[i]];
double t3=p[i-1]*(1-p[i])*dis[d[i-1]][c[i]];
double t4=(1-p[i-1])*p[i]*dis[c[i-1]][d[i]];
double t5=(1-p[i-1])*(1-p[i])*dis[c[i-1]][c[i]];
f[i][j][1]=min( f[i-1][j-1][0]+t1,f[i-1][j-1][1]+t2+t3+t4+t5 );
}
}
double ans=inf;
for ( int i=0; i<=m; i++ )
ans=min( f[n][i][0],min( f[n][i][1],ans ) );
printf( "%.2lf\n",ans );
return 0;
}
P6059 纯粹容器
题意
n 个容器,每个强度为 ai (互不相同).n−1 轮操作,每次等概率选两个位置相邻且未被击倒的容器决斗,强度小的容器会被击倒并移出队列。求每个容器存活轮数的期望 \mod 998244353.
思路
首先特判,对于最大的 a_i ,答案显然是 n-1 (不特判最小的原因是等概率选未必会选到它)。枚举每一个 a_i ,找到距离 a_i 最近且大于它的 a_l,a_r ,显然 a_i 被击倒只有 [l,i] or[i,r] 之间的容器全部决斗。
枚举 a_i 在第 j 轮被淘汰,概率为 p_{i,j} ,那么期望轮数就是 \sum_{1\leq j<n} p_{i,j}\times(j-1)
令 calc(i,j) 表示 n-1 轮决斗中选出 i 轮全部在前 j 轮的概率。显然有 calc(i,j)=\dfrac{j!(n-1-i)!}{(j-i)!(n-1)!}
考虑怎么求 p_{i,j} ,设 g_{i,j} 表示 a_i 在前 j 轮就被淘汰的概率, p_{i,j}=g_{i,j}-g_{i,j-1}
对于 g_{i,j} ,要么被左边淘汰,要么被右边淘汰,容斥得到:
g_{i,j}=calc(i-l,j)+calc(r-i,j)-calc(r-l,j)
(被左边淘汰+被右边淘汰+前 j 轮淘汰了两次)
特判:如果某一边没有比它大的容器,那么只有一边淘汰,就是 calc(i-l,j) 或者 calc(r-i,j)
代码
(居然判输出格式……cena用多了)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=998244353;
const int N=55;
int n,a[N];
ll inv[N],fac[N],g[N][N],ans[N];
ll power( ll a,ll b )
{
ll res=1;
for ( ; b; b>>=1,a=a*a%mod )
if ( b&1 ) res=res*a%mod;
return res;
}
void init()
{
inv[0]=inv[1]=fac[1]=fac[0]=1;
for ( int i=2; i<=n; i++ )
fac[i]=fac[i-1]*i%mod;
inv[n]=power( fac[n],mod-2 );
for ( int i=n; i>1; i-- )
inv[i-1]=inv[i]*i%mod;
}
ll calc( int i,int j )
{
if ( i>j ) return 0;
return fac[j]*fac[n-1-i]%mod*inv[j-i]%mod*inv[n-1]%mod;
}
int main()
{
scanf( "%d",&n );
for ( int i=1; i<=n; i++ )
scanf( "%d",&a[i] );
init(); memset( g,0,sizeof(g) );
for ( int i=1; i<=n; i++ )
{
int l=i-1,r=i+1;
while ( l>=1 && a[l]<a[i] ) l--;
while ( r<=n && a[r]<a[i] ) r++;
if ( l==0 && r==n+1 ) { ans[i]=n-1; continue; }
if ( l==0 )
{
for ( int j=1; j<n; j++ )
g[i][j]=calc( r-i,j );
}
else if ( r==n+1 )
{
for ( int j=1; j<n; j++ )
g[i][j]=calc( i-l,j );
}
else
{
for ( int j=1 ;j<n; j++ )
g[i][j]=(calc( i-l,j )+calc( r-i,j )-calc( r-l,j )+mod)%mod;
}
for ( int j=1; j<n; j++ )
(ans[i]+=(g[i][j]-g[i][j-1]+mod)*(j-1)%mod)%=mod;
}
for ( int i=1; i<=n; i++ )
printf( "%lld ",(ans[i]+mod)%mod );
}
P6835 [Cnoi2020]线形生物
题意
起点为 1 ,走到 n+1 停止,有 n 条边,每条连接 (i,i+1) ;外加 m 条返祖边, (u_i\to v_i)(u_i\ge v_i) 。每一步等概率走向一条出边。求总步数的期望 E(\delta)\mod 998244353.
思路
设 E_{x,x+1} 表示 x\to x+1 的期望步数。那么 ans=\sum_{i=1}^n E_{i,i+1}.
令 d_x 表示 x 的返祖边数目, Edge 表示 x 的返祖边集合。
E_{x,x+1}=\dfrac{1}{d_x+1}\times 1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge} (E_{y,x+1}+1)
将 E_{y,x+1}=\sum_{i=y}^x E_{i,i+1} 代入上面的式子,并化简:
E_{x,x+1}=1+\dfrac{1}{d_x+1} \sum_{(x,y)\in Edge}\sum_{i=y}^x E_{i,i+1}
设一类特殊的 E :E_{x,x+1} 为 f_x ,并记 s_x=\sum_{i=0}^x f_i ,代入上式得到:
f_x=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge} (s_x-s_{y-1})
两边都含有项 f_x ,那么把这一项全部移到等式左边:
f_x=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1})+\dfrac{1}{d_x+1}\times f_x\times d_x
\\\\
f_x\times \dfrac{1}{d_x+1}=1+\dfrac{1}{d_x+1}\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1})
\\\\
f_x=d_x+1+\sum_{(x,y)\in Edge}(s_{x-1}-s_{y-1})
然后维护前缀和即可。时间复杂度 O(n).
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
const ll mod=998244353;
struct edge
{
int to,nxt;
}e[N];
int id,n,m,d[N],tot=0,head[N];
ll f[N],s[N];
void add( int u,int v )
{
tot++; e[tot].to=v; e[tot].nxt=head[u]; head[u]=tot;
}
int main()
{
scanf( "%d%d%d",&id,&n,&m );
for ( int i=1,u,v; i<=m; i++ )
scanf( "%d%d",&u,&v ),add( u,v ),d[u]++;
s[0]=0;
for ( int i=1; i<=n; i++ )
{
f[i]=d[i]+1;
for ( int j=head[i]; j; j=e[j].nxt )
(f[i]+=(s[i-1]-s[e[j].to-1]+mod)%mod)%=mod;
s[i]=(s[i-1]+f[i])%mod;
}
printf( "%lld",s[n] );
}