笔记——概率期望DP·Part1
笔记——概率期望DP·Part1
概率
概率是反映随机事件出现的可能性大小的量度。
一般用 \(A\) 表示事件,\(P(A)\) 为 \(A\) 发生的概率。
性质
-
对于任意一个事件 \(A\):\(P(A)=1-P(\neg A)\)
-
有限可加性:当 \(n\) 个事件 \(A_1,\cdots,A_n\) 两两互不相容时:\(P(A_1\cup...\cup A_n)=P(A_1)+\cdots+P(A_n)\)。
\(\cup\) 是并,在这里相当于逻辑或。
- 当事件 \(A,B\) 满足 \(A\) 包含于 \(B\) 时:\(P(B-A)=P(B)-P(A),P(A)\le P(B)\)
\(B-A\) 就是从 \(B\) 中剔除 \(A\),可以理解为 \(B\) 发生而 \(A\) 不发生。
- 对任意两个事件 \(A\) 和 \(B\),\(P(B-A)=P(B)-P(A\cap B)\)
这条相当于上一条,可以把 \(A\cap B\) 视为一个整体。
\(P(A\cup B)=P(A)+P(B)-P(A\cap B)\)
可以从上一条推出来。
\[\begin{align} P(B-A)&=P(B)-P(A\cap B)\nonumber\\ P(A\cup B-A)&=P(B)-P(A\cap B)\nonumber\\ P(A\cup B)-P(A)&=P(B)-P(A\cap B)\nonumber\\ P(A\cup B)&=P(A)+P(B)-P(A\cap B)\nonumber \end{align} \]
条件概率
条件概率是指事件 \(A\) 在另外一个事件 \(B\) 已经发生条件下的发生概率。条件概率表示为:\(P(A|B)\),读作「在 \(B\) 的条件下发生 \(A\) 的概率」
注意:\(P(A|B)\) 与 \(P(B|A)\) 不同。
\(A\) 与 \(B\) 的联合概率,即共同发生的概率,表示为 \(P(AB)\) 或者 \(P(A,B)\) 或者 \(P(A\cap B)\)
\(\cap\) 是交,这里可以理解为逻辑与。
\(P(AB)=P(A)\times P(B|A)=P(B)\times P(A|B)\)
\(P(A|B)=\frac{P(AB)}{P(B)}\)
统计独立性
当且仅当两个随机事件 \(A\) 与 \(B\) 满足 \(P(A\cap B)=P(A)\times P(B)\) 的时候,它们才是统计独立的,这样联合概率可以表示为各自概率的简单乘积。
同样,对于两个独立事件 \(A\) 与 \(B\) 有 \(P(A|B)=P(A)\) 以及 \(P(B|A)=P(B)\)
换句话说,如果 \(A\) 与 \(B\) 是相互独立的,那么 \(A\) 在 \(B\) 这个前提下的条件概率就是 \(A\) 自身的概率;同样,\(B\) 在 \(A\) 的前提下的条件概率就是 \(B\) 自身的概率。
互斥性
当且仅当 \(A\) 与 \(B\) 满足 \(P(A\cap B)=0\) 且 \(P(A)\ne0\),\(P(B)\ne0\) 的时候,\(A\) 与 \(B\) 是互斥的。
因此,\(P(A|B)=0\),\(P(B|A)=0\)
换句话说,如果 \(B\) 已经发生,由于 \(A\) 不能和 \(B\) 在同一场合下发生,那么 \(A\) 发生的概率为零;同样,如果 \(A\) 已经发生,那么 \(B\) 发生的概率为零。
期望
数学期望(mean)(或均值,亦简称期望)是试验中每次可能结果的概率乘以其结果的总和,是最基本的数学特征之一。它反映随机变量平均取值的大小。
设 \(x\) 的多个随机变量为 \(a_1,a_2,a_3,\cdots,a_n\)
对应的概率为 \(p_1,p_2,p_3\cdots p_n\)
则期望为 \(E(x)=\sum_{i=1}^na_ip_i\)
性质
- \(E(cx)=cE(x)\),\(c\)为常数。
相当于在合式里面乘了一个数 \(c\),将 \(c\) 提出来。
-
\(E(x+y)=E(x)+E(y)\),\(x\) 和 \(y\) 是任意两个随机变量
-
\(E(xy)=E(x)\times E(y)\),\(x\) 和 \(y\) 是相互独立的随机变量
题目
了解了这些基础的东西之后,就该做题了吧!
P1654 OSU!
题意
你有一个长度为 \(n\) 的01串。
这个串的第 \(i\) 位有 \(p_i\) 的概率为 \(1\),\((1-p_i)\) 的概率为 \(0\)。
定义这个串的分数为:这个串中连续的 \(X\) 个 \(1\) 可以产生 \(X^3\) 分
问这个串的期望分数。
做法
先考虑一个简单的东西。
设 \(x3_i\) 表示以 \(i\) 结尾的 \(x^3\) 的期望,求 \(x3_i\)。
容易发现,\((x+1)^3=x^3+3x^2+3x+1\)。
那就是说 \(E(x^3)=E((x-1)^3)+3E((x-1)^2)+3E(x-1)+1\)。
那么我们就可以得到 \(x3_i=(x3_{i-1}+3x2_{i-1}+3x1_{i-1}+1)\times p_i\)。
然后我们设 \(dp_i\) 表示我们要求的答案,前 \(i\) 位的期望分数。
那么 \(dp_i=dp_{i-1}+p_i\times(3x2_{i-1}+3x1_{i-1}+1)\)。
为什么呢?
首先,显然地,一定会继承前 \(i-1\) 的分数,所以加上 \(dp_{i-1}\)。
然后,有 \(p_i\) 的概率是 \(1\),考虑这时产生的贡献。
显然,应该加上 \(x3_i\)。因为会算重,所以减去 \(x3_{i-1}\)。
那么这一部分就是:
那么上面的式子就显然了。
代码
稍微改了下变量名。
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 收集邮票
题意
皮皮想要收集 \(n\) 种不同的邮票,但是他只能到凡凡那里购买,每次只能买一张。购买第 \(k\) 次邮票需要支付 \(k\) 元钱。假设购买到每种邮票的概率都是等概率的 \(1/n\),现在要求计算皮皮收集所有种类的邮票需要花费的钱数的期望。
做法
设 \(dp_i\) 表示已经得到 \(i\) 张邮票,还要花费的期望钱数。
容易发现,\(dp\) 值的转移和已购买次数有关。已购买次数并不好处理。
那么不妨先求一个简单的东西:买到已经有了 \(i\) 张不同邮票,还要购买的期望次数 \(f_i\)。
显然,\(f_i=\frac{i}{n}\times f_i +(1-\frac{i}{n} )\times f_{i+1}\)。
想当然地,当我们求出了需要的次数时,我们就能求出需要的钱数。
钱数为 \(\frac{次数\times(次数+1)}{2}\)。
但这样是过不了样例的。可以类比在第一题中不能直接用 \(x1\) 求出 \(x2\) 和 \(x3\) 的值。
虽然不能直接求出答案,但是我们解决了求出已经购买的次数的问题。
如果我们从后往前计数,就是说,最后一次购买的花费是 \(1\),倒数第二次的花费是 \(2\),以此类推,那么本次的期望花费就是 \(f_{i+1}+1\)。
所以,\(dp_i=\frac{i}{n}(dp_i+f_i+1)+(1-\frac{i}{n})(dp_{i+1}+f_{i+1}+1)\)
容易发现,这里的 \(dp_i\) 需要用到 \(dp_i\) 才能求出。
这是不方便处理的,所以我们对这个式子进行化简。
然后直接转移即可。
代码
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 小魔女帕琪
题意
帕琪拥有 \(7\) 种属性的魔法晶体,第 \(i\) 种晶体有 \(a_i\) 个,每次等概率随机使用一个晶体,施放对应属性的魔法。如果连续施放的 \(7\) 个魔法中魔法的属性互不相同,则触发一次帕琪七重奏。现在帕琪想知道触发帕琪七重奏的期望次数是多少。
等概率选取就是说:如果有两个 \(A\) 一个 \(B\),那么选到 \(A\) 的概率就是 \(\frac23\),因为是对晶体等概率选取,而不是对颜色。
做法
考虑前 \(7\) 个晶体能够释放七重奏的概率:
其中,\(n=\sum_{k=1}^7 a_k\)
因为当固定顺序时,概率为:\(\frac{a_1}{n}\times\frac{a_2}{n-1}\times\frac{a_3}{n-2}\times\frac{a_4}{n-3}\times\frac{a_5}{n-4}\times\frac{a_6}{n-5}\times\frac{a_7}{n-6}\)。
又因为有 \(7!\) 种顺序,所以要乘上 \(7!\)。
容易发现,每一次能够释放七重奏的概率相等。
比方说,求出第 \(2\) 到第 \(8\) 位的概率。
可以考虑枚举第一位选择的魔法,然后计算,发现和第一次就释放七重奏的概率相等。
所以,最后的答案就是:
代码
稍微改了一下变量名。
有毒瘤数据,其中 \(\sum_{k=1}^7a_k=6\),如果不化简的话会出现除以 \(0\) 的情况。
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 深海少女与胖头鱼
题意
题目大意:
有 \(n\) 条带 「圣盾」的「胖头鱼」和 \(m\) 条不带圣盾的胖头鱼。每次等概率对一条存活的胖头鱼造成「剧毒」伤害(直接杀死没有圣盾的胖头鱼)。若有圣盾,免疫本次伤害。免疫伤害后,圣盾被破坏。在一条胖头鱼的圣盾被破坏后,给予其他所有没有死亡且没有圣盾的胖头鱼圣盾。现在求杀死所有的胖头鱼所需的期望次数。答案对 \(998244353\) 取模。
啊对了,这道题 \(n\le 10^{14},m\le 10^6\)。
做法
显然,状态里面要记录有护盾和没有护盾的敌人数量。就是说,\(dp_{ij}\) 表示有 \(i\) 个有护盾的,\(j\) 个没有护盾的的期望次数。
如果打到了护盾上,那么会变成 \((i+j-1,i)\)。因为只有被打到的护盾会消失,其他的都会获得护盾,或者已经拥有护盾。发生这种事情的概率是 \(\frac{i}{i+j}\)。
如果没有打到护盾上,那么会变成 \((i,j-1)\)。因为会直接击败一个没有护盾的。发生这件事的概率是 \(\frac{j}{i+j}\)。
那么就有了转移式子:
这样的复杂度为 \(\Theta(nm)\),可以得到 \(15\) 分的好成绩。
如果能快速地求出 \(dp_{i,1}\) 的值,上面式子的复杂度就可以优化到 \(\Theta(n)\) 了。
容易发现:
观察题解可得, 这个式子化简之后就是:\(dp_{i,1}=\frac{n^2+5n+2}{2}\)。
或者,用矩阵加速递推算这个东西也是很快的。
将这个式子带入之后,就得到了一个 \(\Theta(n)\) 的做法。
因为需要求逆元,所以最终复杂度为 \(\Theta(n\log_2 v)\)。
代码
因为 \(n\le 10^{14}\),还来不及做第一次取余就有可能会溢出,所以读入的时候直接取模就好。
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 提高组] 换教室
题意
题目大意:有 \(n\) 个时间段,每个时间段有一个课程。每个课程有 \(2\) 个教室可选。你需要按时间顺序完成这些课程。有一张 \(v\) 个点的无向联通图表示教室之间的路径和花费的体力值。
你可以提交申请以更换教室。一开始,第 \(i\) 节课必然会在第 \(c_i\) 个教室进行。如果你提交了换教室的申请,你将有 \(k_i\) 的概率在 \(d_i\) 号教室上课,有 \((1-k_i)\) 的概率在 \(c_i\) 号教室上课。
你至多可以提交 \(m\) 份申请。问总体力值期望最小是多少。
\(n,m\le 2000,v\le 300\)。
做法
首先,跑一遍Floyd,求出点与点之间的最短路。
设 \(dp_{i,j,0/1}\) 表示上完前 \(i\) 节课,提交恰好 \(j\) 份申请,第 \(i\) 节课是否提交了申请的最小期望总体力值。
考虑转移。
如果一节课没有提交申请,转移时有两种选择:上节课交了申请和没有交申请。枚举所有情况取最小值即可。
如果一节课交了申请,转移时有两种选择:上节课交了申请和没有交申请。枚举所有情况取最小值即可。
具体的式子太长了,见代码。
代码
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 绿豆蛙的归宿
题意
给出张 \(n\) 个点 \(m\) 条边的有向无环图,起点为 \(1\),终点为 \(n\),每条边都有一个长度,并且从起点出发能够到达所有的点,所有的点也都能够到达终点。
绿豆蛙从起点出发,走向终点。 到达每一个顶点时,如果该节点有 \(k\) 条出边,绿豆蛙可以选择任意一条边离开该点,并且走向每条边的概率为 \(\frac{1}{k}\) 。现在绿豆蛙想知道,从起点走到终点的所经过的路径总长度期望是多少?
做法
先考虑设 \(dp_i\) 表示从 \(1\) 号点到 \(i\) 号点的路径长度期望。
那么转移就要考虑这个点从前面某个点转移来的概率。
这并不好求。
考虑反着定义状态。设 \(dp_i\) 表示从 \(i\) 号点走到 \(n\) 号点的期望花费。
那么 \(dp_i=\sum_{(i,k)\in E}\frac{1}{out_i}(dp_k+w(i,k))\)。
其中,\(out_i\) 表示点 \(i\) 的出度,\(w(u,v)\) 表示边 \((u,v)\) 的边权。
方便起见,使用刷表法。
代码
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 城可以看作一个有 \(n\) 个点 \(m\) 条边的有向无环图。可能存在重边。
zbw 在 B 城随机游走,他会在所有路径中随机选择一条路径,选择所有路径的概率相等。路径的起点和终点可以相同。
定义一条路径的长度为经过的边数,你需要求出 zbw 走的路径长度的期望,答案对 \(998244353\) 取模。
做法
因为选择每一条路径的概率相等,所以考虑求出路径总数和所有路径长度的和。
因为每个点都可以作为起点出现,所以考虑以每一个点为起点,求一遍路径总长和路径总数,然后再加起来。
显然这样的复杂度为 \(\Theta(n^2)\),无法通过此题。
考虑加入一个超级源点 \(S\) 和一个超级汇点 \(T\),\(S\) 像所有点连边,所有点向 \(T\) 连边。
显然,这时原图路径的总数等于从超级源点到超级汇点的路径总数。
那路径总长度等于从超级源点到超级汇点的路径长度总和吗?
容易发现,每条路径都会经过 \((S,s)\) 和 \((t,T)\) 这两条边。
所以,每条路径的长度都多了 \(2\),减去即可。
代码
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 红包发红包
题意
有一个抢红包的系统,红包总金额为 \(w\) 元,共有 \(n\) 个人参与抢红包。
如果红包里还剩 \(w\) 元,那么这次抢到的金额是 \([0,w]\) 区间内的等概率随机实数 \(x\)。
求第 \(k\) 个人期望抢到多少钱,答案对 \(10^9+7\) 取模。
\(k\le n\le 10^{18},w\le 10^{18}\)。
做法
设 \(f_i\) 表示第 \(i\) 个人抢到的钱数。
根据题意,\(f_1=\frac w2\)。
考虑求出其他的 \(f\) 值。如果当前还剩 \(x\) 元,那么这次期望能抢到 \(\frac x2\) 元,期望剩余 \(\frac x2\) 元。
不妨设 \(g_i\) 表示第 \(i\) 个人抢完后,剩余钱数的期望。根据上面的计算,\(f_i=g_i\)。
显然 \(f_i=\frac{g_{i-1}}2=\frac{f_{i-1}}2=\frac{w}{2^i}\)。
使用快速幂求出即可。
代码
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 [国家集训队]单选错位
题意
给定了一组数 \(a_1,a_2,a_3,...,a_n\),其中 \(a_i\) 表示第 \(i\) 道题的选项数。
你做对了所有题,但你抄答案时错位了。具体来说,第 \(i\) 道题的答案被抄到了第 \(i+1\) 道题上(如果 \(i=n\),则答案被抄到了第 \(1\) 道题上)。假设你没有做错任何题目,求你期望能做对多少题。
\(n\le 10^7\)。
做法
容易发现每种情况的概率是相等的,考虑求出所有可能情况数和所有可能情况下做对的题目的总数。
显然,总方案数 \(tot=\prod_{i=1}^na_i\)。不难发现,这个数大得离谱,很容易溢出。
先不着急,往下推式子先。
考虑第 \(i\) 道题有多少种情况能做对。容易发现,只有第 \(i\) 题和 \(i-1\) 题会产生贡献,其他可以随便选。
考虑这两道题,发现总共有 \(a_i\times a_{i-1}\) 中情况。其中有 \(\min(a_i,a_{i-1})\) 种情况可以做对第 \(i\) 题。
那么第 \(i\) 题对通过总题数的贡献就是:
综上,总贡献就是:
这样,溢出的问题就解决了,直接计算即可。
代码
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
题意
有 \(n\) 次点击要做,成功了就是 "o",失败了就是 "x",分数是按 combo 计算的,连续 \(a\) 个 combo 就有 \(a^2\) 分,combo 就是极大的连续 "o"。
现在给定一个字符串,表示 WJMZBMR 在游戏中击打的结果,其中 "?" 号可以代表 "o" 或 "x",各自概率为 \(50\%\)。求 WJMZBMR 的期望得分。
做法
类比第一题。
将o视为有 \(1\) 的概率 \(1\),x为有 \(0\) 的概率为 \(1\),?视为有 \(\frac12\) 的概率为 \(1\)。
和第一题的区别仅仅是从 \(x^3\) 变成了 \(x^2\)。
于是设 \(f_i\) 表示以 \(i\) 结尾的连续 \(1\) 的长度期望,\(dp_i\) 表示要求的答案。
那么 \(f_i=p_i\times(1+f_i-1),dp_i=dp_{i-1}+p_i\times(2\times f_{i-1}+1)\)。
直接转移即可,答案为 \(dp_n\)。
代码
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
题意
有 \(n\) 条鱼,编号从 \(1\) 到 \(n\) ,生活在一个湖中。每天有一对鱼会相遇,并且每对鱼相遇的概率相等。
若两条标号为 \(i\) 和 \(j\) 的鱼相遇,第一只吃了第二只的概率为 \(a_{i,j}\) ,第二只吃了第一只的概率为 \(a_{j,i}=1-a_{i,j}\)。
这样的过程会一直进行下去,直到湖中只剩下一条鱼。请你计算每条鱼最后留在湖中的概率。
\(n\le 18\)。
做法
观察数据范围,考虑状压dp。
设 \(dp(s)\) 表示有且仅有 \(S\) 中的鱼存活的概率。显然初始状态为 \(dp(\text{all 1})=1\)。
考虑转移。枚举上一次被吃掉的鱼和吃掉它的鱼。那么转移就是:
按照式子转移即可。
代码
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 纯粹容器
题意
有 \(n\) 个容器排成了一行,每个容器有一个强度值。
下面会进行进行 \(n-1\) 轮操作,每轮操作等概率随机挑选两个相邻且未被击倒的容器进行决斗,强度小的容器会被击碎。
求每个容器存活轮数的期望,未被击碎的轮数。
做法
观察题解,得到一个美妙的公式:
也就是说,我们不用去求一个容器恰好在第 \(i\) 轮被击碎的概率了,只需要求出一个容器在第 \(i\) 轮仍未被击碎的概率。
容易发现,对于一个容器 \(i\),他会被左边第一个比它大的容器的位置上的容器击碎,或者被右边第一个比它大的容器的位置的容器击碎。因为即使这个位置被击碎了,击碎它的容器也会来到这个位置。
那么不妨计左边第一个比它大的为 \(pre\),右边第一个比它大的为 \(nxt\)。那么 \(i\) 被击碎的概率就是 \(i\) 到 \(pre\) 这边被击碎完(事件 \(A\)),或者 \(i\) 到 \(nxt\) 被击碎完(事件 \(B\))。
那么我们要算出 \(P(A\cup B)=P(A)+P(B)-P(A\cap B)\)。
计算可得:
按照上面的式子计算即可。
代码
//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
题意
一个\(n\)行\(m\)列的矩阵,每个位置有权值\(a_{i,j}\)。
给定一个出发点,每次可以等概率的移动到一个权值小于当前点权值的点,同时得分加上两个点之间欧几里得距离的平方(欧几里得距离:\(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\))),问得分的期望。
做法
首先,按照 \(a_{i,j}\) 从小到大将所有点排序。排序后将所有点重新编号(\(1\le i \le nm\))。
设 \(dp_i\) 表示以 \(i\) 为起点的期望得分。那么转移显然就是遍历能到达的所有点,然后转移。显然转移式子为:
其中,\(lst\) 表示最靠前的,权值和 \(i\) 相等的位置。显然这样的复杂度显然是 \(\Theta(n^2)\) 的。
考虑如何快速转移。显然需要的 \(dp\) 值是一个前缀,记一个前缀和即可。
考虑如何处理每次转移的分数。容易发现(以 \(x\) 坐标为例):
然后直接用前缀和求出即可。
代码
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
题意
有一个 \(m\) 面的骰子,你要进行 \(n\) 次投掷,问投出点数最大值的期望。
做法
考虑求出掷出 \(k(1\le k\le m)\) 的概率。
掷 \(n\) 次取值的总方案数为:\(m^n\)。
掷 \(n\) 次,每次的值都小于等于 \(k\) 的方案数为:\(k^n\)。
掷 \(n\) 次,每次的值都小于等于 \(k-1\) 的方案数为:\((k-1)^n\)。
那么掷 \(n\) 次,至少有一次掷出的值等于 \(k\) 的方案数就是:\(k^n-(k-1)^n\)。
所以最大值为 \(k\) 的概率就是:\(\frac{k^n-(k-1)^n}{m^n}\)。
所以最大值的期望就是:\(\sum_{k=1}^nk\frac{k^n-(k-1)^n}{m^n}\)。
不难发现,这样肥肠容易溢出。所以考虑对这个式子进行化简:
直接计算即可。
代码
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。

浙公网安备 33010602011771号