ICPC2021昆明区域赛 C. Cup of Water 详细题解(多种解法)
QOJ9851 Kunming Regional 2021 C. Cup of Water
现有一个容积为 \(1\) 的杯子,每次你可以在 \([0,x]\) 范围内给定随机实数 \(t\),向杯中倒入 \(t\) 的水。问将杯子倒满的期望次数。
\(T\leq 10000,0.05\leq x\leq 10^9\)
前言
感觉是一道史诗般的概率期望题,这里基于自己的想法再讲一遍,会比较详细。容斥思路一基于博客,思路二基于官方题解。
为了避免后续的变量名混淆,我们将原题中 \(x\) 更改为 \(m\),即 \(t\) 在 \([0,m]\) 中随机。
解法一:暴力积分
若定义 \(f(x)\) 为还需倒 \(x\) 单位水的期望次数,则要求 \(f(1)\),同时根据期望的定义,可以得到:
特别的,当 \(y\leq 0\) 时,\(E(y)=0\)。
当 \(x<m\) 时,式子可以转化为
两端同时对 \(x\) 求导可得微分方程
得通解 \(f(x)=Ce^{\frac{1}{m}x}\),带入原式可得 \(C=1\)。
于是,当 \(x<m\) 时,\(f(x)=e^{\frac{1}{m}x}\)。
当 \(m<x\),难以再求得这样优美的式子,考虑直接暴力求积分。具体做法是将 \([0,1]\) 的区间分成若干小份,求对于每一份的期望值,积分就相当于求区间和。
然而有多测,如果要保证精度无法保证速度,问题出在每次询问 \(m\) 不同。可以将题目转化为:
容积为 \(\frac{1}{m}\) 的杯子,每次可以在 \([0,1]\) 范围内随机倒水,求期望倒水次数。
考虑到 \(\min{m}=0.05\),可以接受。具体实现见代码
const int N=400000;
const double dx=1.0/N;
double f[20*N+5],sf[20*N+5];
int main(){
for(int i=1;i<=N;++i){
f[i]=exp(i*dx);
sf[i]=sf[i-1]+f[i];
}
for(int i=N+1;i<=N*20;++i){
f[i]=1+(sf[i-1]-sf[i-N-1])*dx;
sf[i]=sf[i-1]+f[i];
}
int Test;scanf("%d",&Test);
while(Test--){
double m;scanf("%lf",&m);
m=1/m;
if(m<=1)printf("%.8lf\n",exp(m));
else printf("%.8lf\n",f[(int)(m*N)]);
}
return 0;
}
解法二:容斥
令 \(P(n=k)\) 表示倒 \(k\) 次水刚好倒满的概率,则要求期望
现在考虑求 \(P(n\geq k)\),\(k\in \Z^+\)。
如果我们将每次倒水的取值范围从 \([0,x]\) 拓展为非负实数 \([0,+\infty]\),记第 \(i\) 次倒水的体积为 \(r_i\)。
则这是一个条件概率,有:
将 \(\{r_1,r_2,\cdots,r_k\}\) 看成 \(k\) 维空间中的点,则这个概率的值等于分子分母所对应的点构成的体积之比,即
显然分母 \(V(\forall i,r_i\in[0,x])=x^k\) 可以提出来,分子是一个可以容斥求解的东西。
定义 \(G(j)\) 为至少 \(j\) 个 \(r_i>x\) 且 \(\sum r_i<1\) 的点集体积。则分子:
形式化来说
\[G(j)=V(\sum_{i=1}^{k}r_i<1\and \sum_{i=1}^{k}[r_i>m]\geq j\and r_i\in \R^+) \]在这一步容斥之后,所有 \(r\) 的值便可以在正实数中选取,解决了 \(m\) 的限制。
根据后面的两种思路可以得到(这里略去)
故
可以发现 \(k=0\) 时,\(P(n\geq 1)=1\) 也合法,故答案求和下标可从 \(k=0\) 开始
现在考虑怎么求 \(G(j)\),有以下两种思路
\(G(j)\) 的定义为 \(k\) 个维度中,至少 \(j\) 个维度的坐标 \(r>m\),且满足 \(\sum_{i=1}^{k}r_i<1\) 的点集的体积(或个数,此为思路二)。其中 \(jm>1\) 时体积为 \(0\) 不考虑。(用体积可能不是很严谨,尽量理解吧)
思路一:前缀和有序
钦定某 \(j\) 个变量一定 \(r_{1\sim j}>m\),并将他们的值都减去 \(m\),则现在所有的维度都看作 \([0,\infty]\) 中随机取值。现在有
记 \(s_j=\sum_{i=1}^{j}r_i\),即 \(s\) 是 \(r\) 的前缀和,将 \(s\) 序列也看 \(k\) 维空间中点,则应当有
也就是 \(s\) 可以在 \((1-jm)^k\) 大小的空间内选点,但是每一维的值必须递增,又每一维的值大小排列关系共有 \(k!\) 种,只有 \(\frac{1}{k!}\) 的情况合法。
再乘上 \(\binom{k}{j}\) 则是所有情况。
思路二:离散后划分
将 \([0,1]\) 这个小区间分成无穷且离散的 \(N\) 份,将每个实数 \(r_i\) 看作一个整数 \(z_i\approx Nr_i\)。注意此时的空间定义也有连续变为离散的,即变成合法的点的个数。
(由于变成了一个离散的问题,为了后续推导方便,不认为可以倒 \(0\) 的水;但实际上,由于 \(N\) 无穷的性质,即使可以倒 \(0\) 单位的水也并不影响最终结果)。
分母显然为 \(V(\forall i,z_i\in[1,Nx])=(Nx)^k\)。
分子采取类似的容斥,\(G(j)\) 变为至少 \(j\) 个 \(z>Nx\),且 \(\sum z_i<N\) 的 \(z\) 所对应的 \(k\) 维点个数。
钦定某 \(j\) 个维度 \(z>Nx\) 同时减去 \(Nx\),则现在 \(k\) 个维度都可在 \([1,\infty]\) 中随机取整数,同时加上一项 \(z_{k+1}>0\) 使得原先的和为 \(N\),则有:
求解 \(z\) 的不同方案数,这是一个经典的整数划分问题,可以使用插板法解决。
考虑到 \(\binom{k}{j}\) 种钦定方案和 \(N\) 无穷大的性质,可以得到
加上容斥的过程,最终得到
至此我们说完了两种求解容斥的思路,下为参考代码
double m;scanf("%lf",&m);
double ans=0;
for(ll j=0,pre=1;j*m<=1;++j,pre*=j){
double f=(j&1?-1.0:1.0);
ans+=f/pre*pow((1.0-j*m)/m,j)*exp((1.0-j*m)/m);
}
printf("%.8lf\n",ans);

浙公网安备 33010602011771号