概率与期望瞎做

瞎做,做了一些蓝题,总结一下,就当是,杂题记录吧。哦顺便,这些题来源于 xzy的概率期望题单 中我没做过的蓝题。

P6834 [Cnoi2020]梦原

有个以 \(1\) 为根的 \(n\) 个结点的树,给出参数 \(k\),第 \(i\) 个结点会从 \([i-k,i-1]\cap \mathbb{N}_+\) 中随机选择一个数当做父结点。第 \(i\) 个点的权值是 \(a_i\),定义一次操作为:

  • 选择一个连通块 \(\mathbf S\),满足 \(\forall u\in\mathbf S\),都有 \(a_u>0\),并把连通块内的所有点权值减去 \(1\)

求期望最小操作次数对 \(998,244,353\) 取模的结果。(\(1\le k<n\le 10^6\))

我们先来研究一下最小操作次数这个奇妙的东西,发现这道题和 P5019 [NOIP2018 提高组] 铺设道路 很像,都是选择一个非 \(0\) 连通块进行操作,只不过后者是放在链上的。考虑猜测类似的结论,即答案为:

\[\sum_{u=1}^n\max(a_u-a_{fa_u},0) \]

证明也是类似的,处理 \(u\) 时顺便处理 \(fa_u\) 就好。所以接下来考虑怎么处理随机的树形态。

我们想求的是:

\[E\left(\sum_{u=1}^n\max(a_u-a_{fa_u},0)\right) \]

根据期望的线性性把式子换一换:

\[\sum_{u=1}^nE(\max(a_u-a_{fa_u},0)) \]

也就是说,我们可以对每个结点分别求解。求解的期望根据定义就等于:

\[E(\max(a_u-a_{fa_u},0))=\dfrac{\sum\limits_{i=\max(1,u-k)}^{u-1}\max(a_u-a_i,0)}{u-\max(1,u-k)} \]

现在比较困难的就是维护上面的和式了。

注意到这东西相当于查询区间内所有比 \(a_i\) 小的数的个数和和。这东西我一开始想的主席树,然后发现空间限制 \(\rm 128MB\)。所以还是换做法吧。

首先发现完全没必要上可持久化数据结构,注意到这个区间是单调前进的,类似滑动窗口,在一个元素没用了之后处理掉贡献即可。然后发现完全没必要上动态开点线段树,因为我们要的是大小关系,离散化后可以直接扔到树状数组上维护。这样,空间复杂度就优化到 \(\mathcal{O}(n)\) 了。

具体来讲,两个树状数组,一个维护和一个维护数量即可。时间复杂度 \(\mathcal{O}(n\log n)\)

// 纠结了一会儿放不放代码,最后决定只放主函数。
int main()
{
    int n, k; scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i), t[i] = a[i];
    std::sort(t + 1, t + n + 1); int m = std::unique(t + 1, t + n + 1) - t - 1;
    for (int i = 1; i <= n; ++i) b[i] = std::lower_bound(t + 1, t + m + 1, a[i]) - t;
    int ans = a[1]; bit1.len = bit2.len = m; int now = 1;
    for (int i = 1, div, sum, tot; i <= n; ++i)
    {
        while (now < std::max(i - k, 1)) bit1.add(b[now], -a[now]), bit2.add(b[now], -1), ++now;
        div = ksm(i - std::max(1, i - k), mod - 2);
        sum = bit1.query(b[i]); tot = bit2.query(b[i]);
        sum = (mod - sum + 1ll * a[i] * tot % mod) % mod;
        (ans += 1ll * sum * div % mod) %= mod;
        bit1.add(b[i], a[i]); bit2.add(b[i], 1);
    }
    printf("%d\n", ans); return 0;
}

P6835 [Cnoi2020]线形生物

给出一条 \(n+1\) 个点的链,满足第 \(i(1\le i\le n)\) 个点向第 \(i+1\) 个点连边。除此之外,还有 \(m\) 条返祖边 \(u_i\rightarrow v_i(u_i\le v_i)\)。问每次等概率选择出边行走,从 \(1\) 走到 \(n+1\) 的期望行走次数,答案对 \(998,244,353\) 取模。(\(1\le n,m\le 10^6\))

草,这道题刚做完,第二天就来了到把这个放到 ACAM 上的版本。(那道题只有一个串加入 ACAM,所以算上 \(\sf fail\) 其实是一样的模型)

首先,这是一道随机游走,而且还有环,看起来高消跑不掉了,不过,数据范围 题目中的性质是,边只往前连。看起来这个性质能帮助我们获得一点好的做法。

首先,因为是链,考虑设 \(f_{u}\) 表示从 \(u\) 走到 \(u+1\) 的期望次数,则如果设 \(E(u\rightarrow v)\) 表示从 \(u\) 走到 \(v\) 的期望次数,应该有:

\[E(u\rightarrow v)=\sum_{i=u}^{v-1} f_i \]

最终答案即为 \(E(1\rightarrow n+1)\)。也就是说,我们求出 \(f_{1\sim n}\) 即可。

考虑有关 \(f_u\) 的等式,它或者一步走到合适的位置,或者回到前面的点,即:

\[f_u=1+\dfrac{1}{out_u}\sum_{u\rightarrow v,u\ge v} E(v\rightarrow (u+1)) \]

套刚刚的式子(\(out_u\) 表示出度):

\[f_u=1+\dfrac{1}{out_u}\sum_{u\rightarrow v,u\ge v} \sum_{i=v}^u f_i \]

哇,右边左边都出现了 \(f_u\),但似乎,剩下的 \(f_v\) 都小于 \(u\),也就是说,如果我们整理一下式子,是能够做到递推的!具体来讲,把 \(f_u\) 扔到左边,然后做点简单的代数变换:

\[f_u=out_u+\sum_{u\rightarrow v,u\ge v}\sum_{i=v}^{u-1}f_i \]

注意到 \(f_u\) 会在刚刚的和式里被统计 \(out_u-1\) 次即可得出此式。

剩下的就比较简单了,维护个前缀和即可加速转移。总时间复杂度 \(\mathcal{O}(n+m)\)

int main()
{
    int qwq, n, m; scanf("%d%d%d", &qwq, &n, &m);
    for (int i = 1; i <= n; ++i) out[i] = 1;
    for (int i = 1, u, v; i <= m; ++i) 
        scanf("%d%d", &u, &v), G[u].push_back(v), ++out[u];
    for (int u = 1; u <= n; ++u)
    {
        f[u] = out[u];
        for (auto v : G[u]) (f[u] += (sum[u - 1] - sum[v - 1] + mod) % mod) %= mod;
        sum[u] = (sum[u - 1] + f[u]) % mod;
    }
    printf("%d\n", sum[n]); return 0;
}

CF235B Let's Play Osu!

有长为 \(n\) 的序列,第 \(i\) 个值为 \(1\) 的概率为 \(p_i\)。定义一个序列的权值为,所有极长的连续 \(1\) 子段长度的平方之和。求序列的期望权值。(\(1\le n\le 10^5\))

注意到,期望的线性性 并不允许 我们做出这个推论:

\[E(X^2)=E^2(X) \]

这是 错误的!也就是说,我们不能简单用连续子段长度之和的期望平方来得到答案。

不过我们考虑,如果连续段末尾长度增加 \(1\),会得到多少新增贡献呢?即考虑 \(E((X+1)^2),E(X^2)\) 的关系:

\[E((X+1)^2)=E(X^2+2X+1)=E(X^2)+2E(X)+1 \]

我们发现,如果能求出 \(E(X)\),就能在合理的时间复杂度下递推 \(E(X^2)\) 了。而更进一步,如果我们设 \(f_i\) 表示前 \(i\) 个值的期望权值,\(g_i\) 表示以 \(i\) 结尾的连续子段期望长度(也就是 \(E(X)\)),则有:

\[f_i=f_{i-1}+p_i(2g_{i-1}+1) \]

即考虑每次是否新增贡献。现在我们的任务在于递推 \(g_i\)。考虑每次是否往子段后加入 \(1\) 有:

\[g_i=p_i(g_{i-1}+1) \]

同步递推 \(f_i,g_i\) 即可得到答案 \(f_n\)。时间复杂度 \(\mathcal{O}(n)\)

int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%lf", p + i);
    for (int i = 1; i <= n; ++i)
    {
        f[1][i] = (f[1][i - 1] + 1) * p[i];
        f[2][i] = f[2][i - 1] + p[i] * (f[1][i - 1] * 2 + 1);
    }
    printf("%.9lf\n", f[2][n]); return 0;
}

Bonus:这道题能不能扩展到 \(k\) 次方和呢?(在研究了在研究了.jpg)

CF280C Game on Tree

给定一棵以 \(1\) 为根 \(n\) 个结点的有根树,每次操作定义为:

  • 等概率选择一个尚未删去的结点,删去这个结点和它的子树。

求删去整棵树需要的期望操作次数。(\(1\le n\le 10^5\))

考虑设 \(f_u\) 表示 \(u\) 被选中次数(其中 \(f_u\in\{0,1\}\)),则题目所求即:

\[E\left(\sum_{u=1}^n f_u\right)=\sum_{u=1}^nE(f_u) \]

考虑对每个结点单独求解。注意到,一个点被选中的必要条件是,它的所有祖先结点均未被选中。则,问题又转化为,选择一个操作序列,使得结点 \(u\) 在所有祖先的前面的概率。然后又因为这个概率就是 \(f_u=1\) 的概率,所以 \(p_u=E(f_u)\)

现在的问题变为求出 \(p_u\)。注意到,一个操作序列中有 \(dep_u-1\) 个结点不能在 \(u\) 前面。大概的序列样子就是:

\[\cdots u\cdots fa_u \cdots fa_{fa_u} \cdots\cdots \]

考虑先考虑只存在 \(u\) 和祖先,然后再插入其他的数。只存在 \(u\) 和祖先的话,情况数是:

\[(dep_u-1)! \]

考虑祖先任意排,\(u\) 在前面即可。然后剩下的数插进来的方案数是:

\[(dep_u+1)^{\overline{n-dep_u}} \]

其中 \(n^{\overline{m}}\) 表示 \(n\)\(m\) 次上升幂。乘起来即可得到总方案数:

\[(dep_u-1)!(dep_u+1)^{\overline{n-dep_u}}=\frac{n!}{dep_u} \]

这样,在 \(n!\) 种序列中,选出合法情况的概率即:

\[\frac{\frac{n!}{dep_u}}{n!}=\frac{1}{dep_u} \]

所以,答案即为:

\[\sum_{u=1}^n\frac{1}{dep_u} \]

int main()
{
    int n; scanf("%d", &n);
    for (int i = 1, x, y; i < n; ++i)
        scanf("%d%d", &x, &y), T[x].push_back(y), T[y].push_back(x);
    dfs(1, 0); double ans = 0;
    for (int i = 1; i <= n; ++i) ans += 1.0 / dep[i];
    printf("%.9lf\n", ans); return 0;
}

P4562 [JXOI2018]游戏

共有 \(r-l+1\) 个数,编号从 \(l\)\(r\)。对 \(i\) 进行操作,会导致所有 \(i\) 的倍数被标记。(包括 \(i\) 自己)对于一个 \(l\)\(r\) 的排列,我们定义它的权值为 \(x\),当且仅当只需操作前 \(x\) 个数即可让所有的数被标记。求所有排列权值和,答案对 \(998,244,353\) 取模。(\(1\le l\le r\le 10^7\))

首先,我们可以对 \(l,r\) 做一次类似埃筛的过程,则遇到 \(x\) 时,如果 \(x\) 没有被标记,那么他就必须被操作。也就是说,我们现在的数分成了两类,一类必须被操作,另一类一类是无所谓的。则一个排列的权值就是最后一个必须被操作的数(定义为关键数)所在位置。

考虑先把答案设置为 \((r-l+1)!(r-l+1)\),然后减掉更少的情况对应的贡献。枚举在关键点后面的无所谓数的数量 \(i\),注意到此时对答案贡献为 \(-i\),现在要算的是把 \(r-l+1-key-i\) 个点插入前面关键点的方案数:

\[\dbinom{r-l+1-key}{i}i!key^{\overline{r-l+1-key-i}} \]

前面组合数是选出来 \(i\) 个点,然后是选出来的点的排列和剩下点的插入。

最后,算出总贡献后,乘上 \(key!\) 给最终答案算上即可。乘的 \(key!\) 是关键点内部的排序。时间复杂度 \(\mathcal{O}(r\log \log r)\)

int main()
{
    int l, r; scanf("%d%d", &l, &r); init(N - 1);
    int ans = 1ll * fac[r - l + 1] * (r - l + 1) % mod, key = 0;
    for (int i = l; i <= r; ++i)
    {
        if (vis[i]) continue;
        ++key;
        for (int j = i; j <= r; j += i) vis[j] = 1;
    }
    int res = r - l + 1 - key, sum = 0;
    for (int i = 1; i <= res; ++i) 
        (sum += 1ll * down(res, i) * up(key, res - i) % mod * i % mod) %= mod;
    (ans += mod - 1ll * sum * fac[key] % mod) %= mod;
    printf("%d\n", ans); return 0;
}

P4550 收集邮票

共有 \(n\) 种物品,每次抽取会等概率获得其中一种物品。第 \(i\) 次抽取需要付出 \(i\) 的代价。求想获得全部 \(n\) 种物品的期望代价。(\(1\le n\le 10^4\))

考虑设 \(f_i\) 表示已经获得了 \(i\) 种物品,距离目标还需要的期望代价,初始时 \(f_n=0\)。但我们发现似乎不是很好算贡献。考虑我们求的相当于 \(E(\frac{X^2+X}{2})=\frac{E(X^2)+E(X)}{2}\),发现拆成两部分统计似乎会更舒服。

考虑重新设 \(f_i\) 表示抽取次数的平方的期望,\(g_i\) 表示抽取次数的期望。则 \(g_i\) 的转移比较简单,考虑每次抽取是抽取到已经获得的还是未获得的即可:

\[\begin{aligned}g_i&=\dfrac{i}{n}(g_i+1)+\dfrac{n-i}{n}(g_{i+1}+1)\\g_i&=g_{i+1}+\dfrac{n}{n-i}\end{aligned} \]

然后 \(f_i\) 的转移可以类似考虑上上上一题:

\[\begin{aligned}f_i=E(X_i^2)&=\dfrac{i}{n}E((X_i+1)^2)+\dfrac{n-i}{n}E((X_{i+1}+1)^2)\\E(X_i^2)&=\dfrac{i}{n}E(X_i^2)+\dfrac{2i}{n}E(X_i)+\dfrac{n-i}{n}E(X_{i+1}^2)+\dfrac{2n-2i}{n}E(X_{i+1})+1\\E(X_i^2)&=\dfrac{2i}{n-i}E(X_i)+E(X_{i+1}^2)+2E(X_{i+1})+\dfrac{n}{n-i}\\f_i&=\dfrac{2i}{n-i}g_i+f_{i+1}+2g_i+\dfrac{n}{n-i}\end{aligned} \]

转移出 \(f_i,g_i\) 后,根据期望的线性性,答案即为:

\[\dfrac{f_0+g_0}{2} \]

时间复杂度 \(\mathcal{O}(n)\)

int main()
{
    int n; scanf("%d", &n);
    for (int i = n - 1; ~i; --i)
    {
        f[i] = f[i + 1] + 1.0 * n / (n - i);
        f2[i] = f2[i + 1] + 2 * f[i + 1] + 2.0 * i / (n - i) * f[i] + 1.0 * n / (n - i);
    }
    printf("%.2lf\n", (f[0] + f2[0]) / 2); return 0;
}

CF16E Fish

\(n\) 条鱼,每天都有一对鱼相遇。如果第 \(i\) 条鱼遇到第 \(j\) 条鱼,那么前者吃掉后者的概率为 \(a_{i,j}\),后者吃掉前者的概率为 \(a_{j,i}=1-a_{i,j}\)。求每条鱼在 \(n-1\) 天后存活的概率。(\(1\le n\le 18\))

数据范围提示我们这道题可以考虑状态压缩。考虑设 \(f_S\) 表示鱼存活状态为 \(S\) 的概率,初始时 \(f_{\{1,1,1,\cdots\}}=1\)。(不用再额外多一位天数,可以根据 \(1\) 的数量判断)

转移时考虑枚举这一条哪条鱼被吃掉和谁吃掉它的即可。考虑前者为 \(i\),后者为 \(j\),则有转移:

\[f_{S'}\leftarrow f_Sa_{j,i}tot^{-1} \]

其中 \(\leftarrow\) 表示加上贡献,\(tot=\frac{|S|^2-|S|}{2}\)\(S'\) 表示 \(S\) 中,第 \(i\) 位为 \(0\),其余位置值不变对应的集合。

最终答案第 \(i\) 条鱼对应答案即为 \(f_{\{0,0,0\cdots 1,0,\cdots\}}\),其中 \(S_i=1\)。时间复杂度 \(\mathcal{O}(2^nn^2)\)

int main()
{
    int n, all; scanf("%d", &n); all = (1 << n) - 1;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j) scanf("%lf", &a[i][j]);
    f[all] = 1;
    for (int S = all; S; --S)
        for (int i = 0; i < n; ++i)
        {
            if (!(S & (1 << i))) continue;
            int nxt = S ^ (1 << i), tot = pcnt(S); tot = tot * (tot - 1) / 2;
            db tmp = 0;
            for (int j = 0; j < n; ++j)
            {
                if (!(S & (1 << j)) || i == j) continue;
                tmp += a[j][i] / tot;
            }
            f[nxt] += tmp * f[S];
        }
    for (int i = 0; i < n; ++i) printf("%.6lf ", f[1 << i]);
    puts(""); return 0;
}

CF453A Little Pony and Expected Maximum

\(n\) 个在 \([1,m]\cap\mathbb{Z}\) 中等概率随机的变量最大值的期望。(\(1\le n,m\le 10^5\))

吐槽:为啥这题不用取模啊,明明要算 \(m^n\) 这种级别的数的说。

考虑每个数作为最大值出现的概率,然后根据离散期望的定义即可得到答案。注意到数 \(x\) 能作为最大值,当且仅当所有数均落在 \([1,x]\) 里,且至少存在一个 \(x\)。所有数均落在 \([1,x]\) 的概率比较好算,就是 \((\frac{x}{m})^n\)。而至少存在一个 \(x\) 这个限制,比较难受。考虑补集转化,不合法的情况为所有数均落在 \([1,x-1]\) 里,而这个的概率为 \((\frac{x-1}{m})^n\),二者相减即可得到 \(x\) 作为最大值的概率。所以最终答案即:

\[\sum_{i=1}^mi\left(\left(\dfrac{i}{m}\right)^n-\left(\dfrac{i-1}{m}\right)^n\right) \]

直接计算即可。时间复杂度 \(\mathcal{O}(m\log n)\)

int main()
{
    int m, n; scanf("%d%d", &m, &n); db ans = 0;
    for (db i = 1; i <= m; ++i) ans += i * (ksm(i / m, n) - ksm((i - 1) / m, n));
    printf("%.6Lf\n", ans); return 0;
}

CF1042E Vasya and Magic Matrix

一个 \(n\times m\) 的矩阵,每个点有权值 \(a_{i,j}\)。从 \((x,y)\) 出发,每次会等概率选择一个权值比自己小的点,走到那个点。这个过程会一直持续到无法进行。每次从 \((i,j)\) 走到 \((i',j')\) 会获得 \((i-i')^2+(j-j')^2\) 的权值。求获得权值的期望。(\(1\le n,m\le 10^3,0\le a_{i,j}\le 10^9\))

考虑设 \(f_{i,j}\)\((i,j)\) 出发能得到权值期望。则转移比较明显:

\[f_{i,j}=\dfrac{1}{\sum\limits_{a_{x,y}<a_{i,j}}1}\sum_{a_{x,y}<a_{i,j}}(f_{x,y}+(x-i)^2+(y-j)^2) \]

这东西朴素做的话是 \(\mathcal{O}((nm)^2)\) 的,无法接受。考虑优化,注意到能转移的区间是一段前缀,所以考虑前缀和优化转移。

首先比较好优化的是 \(\sum f_{x,y}\),直接维护就好。而后面两个平方式需要稍微拆一下,不过也不麻烦:

\[\begin{aligned}\sum_{a_{x,y}<a_{i,j}}(x-i)^2&=\sum_{a_{x,y}<a_{i,j}}(x^2-2xi+i^2)\\&=\sum\limits_{a_{x,y}<a_{i,j}}i^2-2i\sum_{a_{x,y}<a_{i,j}}x+\sum_{a_{x,y}<a_{i,j}}x^2\end{aligned} \]

\((y-j)^2\) 是类似的。发现第一项可以直接求,后面的两个和式也可以朴素维护前缀和。这样转移就可以做到 \(\mathcal{O}(1)\) 了,总时间复杂度 \(\mathcal{O}(nm)\)

int main()
{
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 1, x; i <= n; ++i)
        for (int j = 1; j <= m; ++j) 
            scanf("%d", &x), a[++tp].w = x, a[tp].x = i, a[tp].y = j;
    int X, Y; scanf("%d%d", &X, &Y);
    std::sort(a + 1, a + tp + 1, [](const node& x, const node& y) { return x.w < y.w; });
    int beg = 1, preX = 0, preX2 = 0, preY = 0, preY2 = 0, pre = 0, inv;
    while (a[beg + 1].w == a[beg].w)
    {
        if (a[beg].x == X && a[beg].y == Y) return puts("0"), 0;
        (preX += a[beg].x) %= mod, (preX2 += (ll)a[beg].x * a[beg].x % mod) %= mod;
        (preY += a[beg].y) %= mod, (preY2 += (ll)a[beg].y * a[beg].y % mod) %= mod;
        ++beg;
    }
    (preX += a[beg].x) %= mod, (preX2 += (ll)a[beg].x * a[beg].x % mod) %= mod;
    (preY += a[beg].y) %= mod, (preY2 += (ll)a[beg].y * a[beg].y % mod) %= mod;
    inv = ksm(beg, mod - 2); ++beg;
    for (int i = beg; i <= tp; ++i)
    {
        f[i] = (ll)inv * ((pre + (ll)preX2 + preY2 - 2ll * a[i].x * preX % mod - 
        2ll * a[i].y * preY % mod + (ll)a[i].x * a[i].x % mod * (beg - 1) % mod
        + (ll)a[i].y * a[i].y % mod * (beg - 1) % mod) % mod) % mod;
        (f[i] += mod) %= mod;
        if (a[i + 1].w != a[i].w)
        {
            for (int j = beg; j <= i; ++j)
                (preX += a[j].x) %= mod, (preX2 += (ll)a[j].x * a[j].x % mod) %= mod,
                (preY += a[j].y) %= mod, (preY2 += (ll)a[j].y * a[j].y % mod) %= mod,
                (pre += f[j]) %= mod;
            inv = ksm(i, mod - 2); beg = i + 1;
        }
    }
    for (int i = 1; i <= tp; ++i)
        if (a[i].x == X && a[i].y == Y) return printf("%d\n", f[i]), 0;
    return 0;
}

P6046 纯粹容器

给出长为 \(n\) 的序列 \(a\),共 \(n-1\) 轮操作,每轮操作等概率选择两个相邻的数,删去那个较小的数。求每个数期望存在轮数。(\(1\le n\le 50,1\le a_i\le n,\forall i\ne j\),都有 \(a_i\ne a_j\))

这题需要用到一个技巧(假设随机变量 \(X\) 取值的下界为 \(1\)):

\[E(X)=\sum_{i\ge 1}P(X\ge i) \]

证明也比较简单,考虑:

\[E(X)=\sum_{i\ge 1}iP(X=i)=\sum_{i\ge 1}\sum_{j\ge i} P(X=j)=\sum_{i\ge 1}P(X\ge i) \]

其中第二步是考虑每个概率最终会算多少次。

代入本题中,随机变量即为存在轮数,答案即为:

\[\sum_{i=1}^{n-1}P(X\ge i) \]

现在的问题是怎么求 \(P(X\ge i)\)

考虑算出数 \(a_x\) 在前 \(i\) 轮被删去的概率,然后用 \(1\) 减去即得 \(P(X\ge i)\)。注意到 \(a_x\) 被删除,一定是要么是被左边离它最近的 \(a_j(a_j>a_x)\) 和右边离它最近的 \(a_k(a_k>a_x)\) 其中一个删去的,或被把这两个删去的其他数删去。不管是哪一种,前提条件,也是充要条件是,\(x\)\(j,k\) 相邻,且被选中。

考虑设 \(P(x\sim k,i),P(j\sim x,i)\) 分别表示 \(i\) 次操作,\(x\sim k\) 中除了 \(k\) 以外的数全部被删除的概率, \(P(j\sim x,i)\) 同理。则概率为:

\[P(x\sim k,i)=\frac{\binom{n-1-(k-x)}{i-(k-x)}}{\binom{n-1}{i}} \]

考虑下面是总情况,上面是除了 \(k-x\) 个一定要选的,选出剩下的。类似地:

\[P(j\sim x,i)=\frac{\binom{n-1-(x-j)}{i-(x-j)}}{\binom{n-1}{i}} \]

而如果只是单纯相加,我们还会算重一种情况。即 \(i\) 次操作后 \(j\sim k\) 中,除了 \(j,k\) 中较大的那个,全部被删除的情况。设这种情况概率为 \(P(j\sim k,i)\),则:

\[P(j\sim k,i)=\frac{\binom{n-1-(k-j)}{i-(k-j)}}{\binom{n-1}{i}} \]

这样,最终我们能得到:

\[\begin{aligned}P(X\ge i)&=1-P(x\sim k,i)-P(j\sim x,i)+P(j\sim k,i)\\&=1-\frac{\binom{n-1-(k-x)}{i-(k-x)}}{\binom{n-1}{i}}-\frac{\binom{n-1-(x-j)}{i-(x-j)}}{\binom{n-1}{i}}+\frac{\binom{n-1-(k-j)}{i-(k-j)}}{\binom{n-1}{i}}\end{aligned} \]

注意如果 \(j\)\(k\) 不存在,那就不考虑有关它的项就好了。预处理个组合数,就可以做到 \(\mathcal{O}(n^2)\) 求解了。

upd. 刚刚发现这点没说清楚。概率是这么考虑的,虽然我们是有序的操作序列,但如果分子分母都变成无序的那也就无所谓了。(都少一个 \(i!\))然后分子就是,有一些必须选,要选出剩下无所谓的一些操作。

int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", a + i);
    for (int i = 1; i <= n; ++i)
    {
        for (int j = i - 1; j; --j)
            if (a[j] > a[i]) { lef[i] = i - j; break; }
        for (int j = i + 1; j <= n; ++j)
            if (a[j] > a[i]) { rig[i] = j - i; break; }
    }
    C[0][0] = 1;
    for (int i = 1; i <= n; ++i)
    {
        C[i][0] = 1;
        for (int j = 1; j <= i; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
    }
    for (int i = 1, tmp, A, B, AB; i <= n; ++i)
    {
        tmp = 0;
        for (int j = 1; j < n; ++j)
        {
            A = B = AB = 0;
            if (lef[i] && j - lef[i] >= 0) 
                A = (ll)C[n - 1 - lef[i]][j - lef[i]] * ksm(C[n - 1][j], mod - 2) % mod;
            if (rig[i] && j - rig[i] >= 0)
                B = (ll)C[n - 1 - rig[i]][j - rig[i]] * ksm(C[n - 1][j], mod - 2) % mod;
            if (lef[i] && rig[i] && j - lef[i] - rig[i] >= 0)
                AB = (ll)C[n - 1 - lef[i] - rig[i]][j - lef[i] - rig[i]] * ksm(C[n - 1][j], mod - 2) % mod;
            (tmp += (1ll + mod - A + mod - B + AB) % mod) %= mod;
        }
        printf("%d ", tmp);
    }
    puts(""); return 0;
}
posted @ 2022-04-01 20:32  zhiyangfan  阅读(83)  评论(0编辑  收藏  举报