「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\),求
\]
答案对给定的正整数 \(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\)。
\]
先不管 \(g_n\),继续推导 \(f_n\) 的式子:
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!}\) 看成一个整体,发现转移式的主体是一个前缀和。记 \(F_n\) 为 \(\frac {f_i}{i!}\) (\(i\ge1\))的前缀和,则式子可以简化为:
\]
\(F_0=0\),多次迭代过后可以得到 \(F_n\) 的通项。
\]
有一个类似于调和级数前 \((n+1)\) 项的东西,设调和级数前 \(n\) 项为 \(H_n\)。
F_n=(n+1)(H_n-1)&\tag{1}
\end{align}
\]
现在回头看一看 \(g_n\),大致转移与 \(f_n\) 相同,但是根的贡献是 \(f_n\),也即 \(siz_n\) 的期望值(所以先推导 \(f\))。
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)\) 代入。
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=n!(G_n-G_{n-1})\),由于 \(mod\) 不一定是质数,那还得继续推式子。
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!
「SOL」打扫笛卡尔cartesian (模拟赛)的更多相关文章
- 「HGOI#2019.4.19省选模拟赛」赛后总结
t1-Painting 这道题目比较简单,但是我比较弱就只是写了一个链表合并和区间DP. 别人的贪心吊打我的DP,嘤嘤嘤. #include <bits/stdc++.h> #define ...
- 笛卡尔遗传规划Cartesian Genetic Programming (CGP)简单理解(1)
初识遗传算法Genetic Algorithm(GA) 遗传算法是计算数学中用于解决最优化的搜索算法,是进化算法的一种.进化算法借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传.突变.自然选 ...
- 笛卡尔树Cartesian Tree
前言 最近做题目,已经不止一次用到笛卡尔树了.这种数据结构极为优秀,但是构造的细节很容易出错.因此写一篇文章做一个总结. 笛卡尔树 Cartesian Tree 引入问题 有N条的长条状的矩形,宽度都 ...
- 「CSP-S模拟赛」2019第四场
「CSP-S模拟赛」2019第四场 T1 「JOI 2014 Final」JOI 徽章 题目 考场思考(正解) T2 「JOI 2015 Final」分蛋糕 2 题目 考场思考(正解) T3 「CQO ...
- 「NOWCODER」CSP-S模拟赛第3场
「NOWCODER」CSP模拟赛第3场 T1 货物收集 题目 考场思路即正解 T2 货物分组 题目 考场思路 题解 60pts 算法:一维 DP 100pts 算法:一维 DP ?线段树 + 单调栈 ...
- #10471. 「2020-10-02 提高模拟赛」灌溉 (water)
题面:#10471. 「2020-10-02 提高模拟赛」灌溉 (water) 假设只有一组询问,我们可以用二分求解:二分最大距离是多少,然后找到深度最大的结点,并且把它的\(k\)倍祖先的一整子树删 ...
- #10470. 「2020-10-02 提高模拟赛」流水线 (line)
题面:#10470. 「2020-10-02 提高模拟赛」流水线 (line) 题目中的那么多区间的条件让人感觉极其难以维护,而且贪心的做法感觉大多都能 hack 掉,因此考虑寻找一些性质,然后再设计 ...
- POJ 2201 Cartesian Tree ——笛卡尔树
[题目分析] 构造一颗笛卡尔树,然后输出这棵树即可. 首先进行排序,然后用一个栈维护最右的树的节点信息,插入的时候按照第二关键字去找,找到之后插入,下面的树成为它的左子树即可. 然后插入分三种情况讨论 ...
- 「CSP-S模拟赛」2019第三场
目录 T1 「POI2007」山峰和山谷 Ridges and Valleys 题目 考场思路(几近正解) 正解 T2 「JOI 2013 Final」 现代豪宅 题目 考场思路(正解) T3 「SC ...
- 「CSP-S模拟赛」2019第一场
目录 T1 小奇取石子 题目 考场思路 正解 T2 「CCO 2017」专业网络 题目 考场思路 题解 T3 「ZJOI2017」线段树 题目 考场思路 正解 这场考试感觉很奇怪. \(T1.T2\) ...
随机推荐
- Python 装饰器原理
装饰器是 Python 编程中常用的一个功能,可以将通用的逻辑抽象成装饰器,通过装饰器语法应用到不同的目标上,达到增强或修改目标逻辑的目的. 先来看一个简单的例子 # 打印耗时的装饰器 def log ...
- KingbaseES函数三态
理解函数的三态1 VOLATILE: volatile函数没有限制,可以修改数据(如执行delete,insert,update), 使用同样的参数调用可能返回不同的值. STABLE: 不允许修改数 ...
- FAQ docker进程启动失败处理案例分享
docker进程启动失败处理 背景 有同学反馈在启动docker的时候遇到了如下问题:docker启动报错 [root@wuxianfeng ~]# systemctl start docker Jo ...
- Linux文件常用操作命令
Linux文件常用操作命令 一.Linux文件和目录简单操作 1.1 查看文件 ls 查看当前目录下的文件 如: -a 显示所有文件及目录 (ls内定将文件名或目录名称开头为"." ...
- 解决 Vue3 中路由切换到其他页面再切换回来时 Echarts 图表不显示的问题
问题复现: 正常状态下: 切换到其他页面再切换回来: 问题解决: 其实这个问题的解决方式官网写得清清楚楚,我们看看官网怎么解决的: 接下来我用代码解释下这句话(正确的做法是,在图表容器被销毁之后,调用 ...
- P27_wxss - 全局样式和局部样式
全局样式 定义在 app.wxss 中的样式为全局样式,作用于每一个页面. 局部样式 在页面的 .wxss 文件中定义的样式为局部样式,只作用于当前页面. 注意: 当局部样式和全局样式冲突时,根据就近 ...
- Docker挂载
1.挂载的概念 预备:你需要了解docker的基本知识 docker实现了容器部署,那当我们需要配置或者查看容器生成的日志文件怎么办? docker提供了挂载机制:挂载能够将容器内的目录/文件和外部的 ...
- 合肥光源储存环束流三维参数测量系统相关PV
合肥光源纵向震荡数据源相关PV 合肥光源纵向震荡数据源相关PV的增补 在上两文中公布了一些PV,依然有效. 本来发过了,那篇里的PV有些命名的不太好,比如PositionX.PositionY等,感觉 ...
- Android:LitePal 在第一次创建表之后第二次创建新的表不生效
因为业务需求的增长,后续需要继续创建新的表,有可能代码没有任何报错,同时数据库也没有任何新的表加入进来. 修改 litepal.xml 的 version,如果之前是 1,那么修改为 2,总之比之前 ...
- Canvas:绘制多边形
前言 CanvasRenderingContext2D 没有提供绘制多边形的函数,所以只能由我们自己来实现绘制多边形的函数.以六边形为基础,需要用到三角函数:sin 和 cos. 点 A 坐标 (一) ...