「SOL」打扫笛卡尔cartesian (模拟赛)

为什么会有人推得出来第三题想不出来签到题啊 (⊙_⊙)?

题面

有一棵有根树 \(T\)。从根节点出发,在点 \(u\) 时,设点 \(u\) 还有 \(d\) 个未访问过的儿子,则有 \(\frac1{d+1}\) 的概率向上(深度较小的方向)走一步,有 \(\frac1{d+1}\) 的概率走向一个未访问过的儿子。从根节点往上走则结束游走。

\(f(T)\) 为这样游走到达的点的深度之和的期望。

给定 \(N\)\(N\le10^7\)),对 \((1,2,\dots,N)\) 的所有排列 \(P\),建立小根的笛卡尔树 \(T_P\),求

\[\sum_{P}f(T_p) \]

答案对给定的正整数 \(mod\)\(n\lt mod\le2\times10^9\))取模,\(mod\) 不一定是质数。


解析

先分析在 \(T\) 上的游走方法。笛卡尔树是二叉树,若当前点有未访问的儿子,则:

  • 只有一个儿子时,有 \(\frac 12\) 的概率会走向该儿子;
  • 有两个儿子时,有 \(\frac 13\) 的概率第一次就走向该儿子,有 \(\frac 13\times\frac 12\) 的概率第二次走向该儿子,即总共有 \(\frac 12\) 的概率会走向该儿子。

于是我们发现是否会到达一个儿子的概率恒为 \(\frac12\),与儿子个数无关,这会使我们之后的推导方便很多。

考虑到笛卡尔树本身是一个分治结构——从最小值处划分为两个区间分别建笛卡尔树,而一个排列建立笛卡尔树仅仅与排列的元素个数有关。由此可以设计一个以排列元素大小为状态的 DP。

\(g_n\) 表示「对 \(n\) 个元素的所有排列 \(P_n\) 建立笛卡尔树 \(T_{P_n}\),其 \(f(T_{P_n})\) 之和」,\(g_N\) 即我们要求的答案。但是深度之和并不好直接计算(尽管可以用期望的线性性拆成单点的贡献,但是之后的推导会绕一个大圈,不如下面的方法直观)。

有一个非常常用的性质:\(\sum dep=\sum siz\),于是设计辅助 DP \(f_n\) 表示「对 \(n\) 个元素的所有排列 \(P_n\) 建立笛卡尔树 \(T_{P_n}\),从根出发期望能够到达多少个点」。

转移则考虑枚举左子树的大小 \(l\),选出左子树的元素 \(\binom{n-1}{l}\)。利用期望的线性性,左子树的贡献为 \(f_l\) 乘上右子树的方案数,一个排列显然和一棵笛卡尔树一一对应,所以贡献即为 \(f_l\times(n-l-1)\)。右子树同理,最后还要加上根的贡献,对于 \(n!\) 种笛卡尔树根的贡献都是 \(1\)

\[f_n=n!+\frac 12\sum_{l=0}^{n-1}\binom{n-1}{l}\Big((n-l-1)!f_l+l!f_{n-l-1}\Big) \]

先不管 \(g_n\),继续推导 \(f_n\) 的式子:

\[\begin{aligned} f_n&=n!+\sum_{l=0}^{n-1}\binom{n-1}{l}(n-l-1)!f_l\&=n!+\sum_{l=0}^{n-1}(n-1)!\frac{f_l}{l!} \end{aligned} \]

这么多阶乘容易让人联想到指数生成函数的样子,不妨化一下:

\[\frac{f_n}{n!}=1+\frac 1n\sum_{l=0}^{n-1}\frac{f_l}{l!} \]

显然可以把 \(\frac{f_n}{n!}\) 看成一个整体,发现转移式的主体是一个前缀和。记 \(F_n\)\(\frac {f_i}{i!}\)\(i\ge1\))的前缀和,则式子可以简化为:

\[F_n-F_{n-1}=1+\frac 1nF_{n-1}\to F_n=1+\frac{n+1}{n}F_{n-1} \]

\(F_0=0\),多次迭代过后可以得到 \(F_n\) 的通项。

\[F_n=\sum_{i=2}^{n+1}\frac{n+1}{i} \]

有一个类似于调和级数前 \((n+1)\) 项的东西,设调和级数前 \(n\) 项为 \(H_n\)

\[\begin{align} F_n=(n+1)(H_n-1)&\tag{1} \end{align} \]

现在回头看一看 \(g_n\),大致转移与 \(f_n\) 相同,但是根的贡献是 \(f_n\),也即 \(siz_n\) 的期望值(所以先推导 \(f\))。

\[\begin{aligned} g_n&=f_n+\frac12\sum_{l=0}^{n-1}\binom{n-1}{l}\Big((n-l-1)!g_l+l!g_{n-l-1}\Big)\&=f_n+\sum_{l=0}^{n-1}\binom{n-1}{l}(n-l-1)!g_{l}\&=f_n+\sum_{l=0}^{n-1}(n-1)!\frac{g_l}{l!}\&=n!+\sum_{l=0}^{n-1}(n-1)!\frac{g_l+f_l}{l!} \end{aligned} \]

同样的,我们记 \(G_n\)\(\frac{g_i}{i!}\) 的前缀和,把 \((1)\) 代入。

\[\begin{align} G_n-G_{n-1}&=1+\frac 1n(F_{n-1}+G_{n-1})\notag\&=H_n+\frac 1nG_{n-1}&\notag\\to G_n&=H_n+\frac{n+1}{n}G_{n-1}&\tag{2} \end{align} \]

\((2)\) 进行迭代也可以得到 \(G_n\) 的通项公式:

\[G_n=\sum_{i=1}^{n}\frac{n+1}{i+1}H_i \]

我们要算的答案是 \(g_n=n!(G_n-G_{n-1})\),由于 \(mod\) 不一定是质数,那还得继续推式子。

\[\begin{aligned} g_n&=n!\Big(H_n+\sum_{i=1}^{n-1}\frac{H_i}{i+1}\Big)\&=n!H_n+n!\sum_{i=2}^{n}\frac{1}{i}\sum_{j=1}^{i-1}\frac{1}{j}\&=n!H_n+\sum_{1\le i\lt j\le n}\frac{n!}{ij} \end{aligned} \]

这样分母就可以全部抵消了,预处理调和级数前 \(n\) 项系数的前缀和与后缀和可以 \(\mathcal O(n)\) 求解。


源代码

/* Lucky_Glass */
#include <cstdio>
#include <cstring>
#include <algorithm>

const int N = 1e7 + 10;
typedef long long llong;

int mod;

inline int reduce(llong key) {
  return int((key %= mod) < 0 ? key + mod : key);
}

int pre[N], suf[N];

int main() {
  freopen("cartesian.in", "r", stdin);
  freopen("cartesian.out", "w", stdout);

  int n; scanf("%d%d", &n, &mod);

  pre[0] = 1;
  for (int i = 1; i <= n; ++i) pre[i] = reduce(1ll * pre[i - 1] * i);
  suf[n + 1] = 1;
  for (int i = n; i; --i) suf[i] = reduce(1ll * suf[i + 1] * i);

  int ans = 0;
  for (int i = 1; i <= n; ++i)
    ans = reduce(ans + 1ll * pre[i - 1] * suf[i + 1]);

  int ex_ans = 0;
  for (int i = 2, tmp = 0; i <= n; ++i) {
    tmp = reduce(pre[i - 2] + (i - 1ll) * tmp);
    ex_ans = reduce(ex_ans + 1ll * tmp * suf[i + 1]);
  }

  ans = reduce(1ll * ans + ex_ans);
  printf("%d\n", ans);
  return 0;
}

THE END

Thanks for reading!

霓虹中 错落影像
满城声色褪去喧嚷
废墟上 余碑文几行
未铭记何谈淡忘

——《岁月成碑》By 乐正绫/Days

> Link 岁月成碑 - 网易云

「SOL」打扫笛卡尔cartesian (模拟赛)

上一篇:HCNA Routing&Switching之路由基础


下一篇:DS:顺序栈