卡特兰数、Prufer 序列、BSGS 总结
卡特兰数
定义
给定
通项公式:
递推公式:
卡特兰数:
一些结论
- 在直角坐标系上,每一步只能向上或向右走,从
走到 并且除两端点为不越过直线 的路线数量为 。 - 同上问题,从
走到 并且不接触直线 的路线数量为 。 - 同上问题,从
走到 并且不越过直线 的路线数量为 。 个左括号和 个右括号组成的合法括号序列数量为 。 经过一个栈,形成的合法出栈序列的数量为 。 个结点构成的不同二叉树的数量为 。
卡特兰数问题特征
计数问题、排列等,都是卡特兰数问题的特征。
遇到这类可以向卡特兰数方向考虑的问题,可以先手推一遍前几个数,看是否符合卡特兰数列。很多题看着复杂,实际上就是卡特兰数模板题。
有些题不给模数,容易酿成高精度,需要注意。一般高精度的处理方法就是分解质因数,化高精乘除法为加减法,最后只需要一个高精乘低精就可解决。顶多再写一个高精加减低精就可以。笔者在刚打到这里时总是想着码一个万能高精模板出来,但最后发现不仅码量庞大且没有必要,更多时候还是见招拆招,万能模板看似适用,实际上实用性低。
Prufer 序列
Prufer 序列是一种将带标号的树用唯一的一个整数序列表示的方法,二者一一对应。不考虑含有一个结点的树。
定义
Prufer 序列可以将一个
这是一棵
对树建立 Prufer 序列
用堆可以做到
线性构造的本质就是维护一个指针指向我们将要删除的结点。首先发现,删去一个叶结点,叶结点总数要么不变要么减
于是维护一个指针
正确性
实现
void solve1() {
for (int i = 1; i <= n - 1; i++) cin >> fa[i], deg[fa[i]]++;
for (int i = 1, p = 1; i <= n - 2; i++, p++) {
while (deg[p]) p++; // 自增找到下一个叶子结点
pf[i] = fa[p]; // 加入序列
while (i <= n - 2 && --deg[pf[i]] == 0 && pf[i] < p) // 如果产生新叶子结点且编号更小
pf[i + 1] = fa[pf[i]], i++;
}
}
Prufer 序列重构树
与上面同理的,逆运算。注意因为 Prufer 序列只有
void solve2() {
for (int i = 1; i <= n - 2; i++) cin >> pf[i], deg[pf[i]]++;
pf[n - 1] = n;
for (int i = 1, p = 1; i <= n - 2; i++, p++) {
while (deg[p]) p++;
fa[p] = pf[i];
while (i <= n - 2 && --deg[pf[i]] == 0 && pf[i] < p)
fa[pf[i]] = pf[i + 1], i++;
}
}
Prufer 序列的性质
- 构造完 Prufer 序列后原树会剩下两个结点,其中一个一定是编号最大的结点
。 - 每个结点在序列中出现的次数是其度数减
。(没有出现的就是叶节点)
第二点十分重要,是后文的依据。
凯莱公式
完全图
用 Prufer 序列证明十分容易。首先,完全图,说明每个结点都有可能出现在 Prufer 序列的每个位置上。最重要的是,从图推树,形态是不限的,也就是说哪怕是
一些结论
- 一个
个点 条边的带标号无向图有 个连通块,我们希望添加 条边使图联通。方案数为:
其中
- 一个无向连通图在给定所有点度情况下的生成树数量为:
理解 1:由 Prufer 序列性质 2,度数为
理解 2:尝试解释凯莱公式中的
- 同上问题,只给定某些点的度数
,求生成树数量为:
具体参考这篇题解。
总而言之,Prufer 序列本质上是一个工具,其发明出来的目的就是为了证明凯莱公式,本身意义不大。Prufer 序列给我们的帮助更多的是提供了一种解决关于树的计数问题的思路,而不在于其与树的编解码过程(当然代码也不难背是吧。
BSGS
BSGS 算法,全称 Baby Step, Giant Step 算法,是一种用于求解形如
原理
因为
设
对于所有的
枚举
实现
int bsgs(int a, int b, int p) {
unordered_map<int, int> h;
h.clear();
b %= p;
int t = ceil(sqrt(p));
for (int j = 0; j < t; j++) {
int val = b * power(a, j, p) % p;
h[val] = j;
}
a = power(a, t, p);
if (a == 0) return b == 0 ? 1 : -1;
for (int i = 0; i <= t; i++) {
int val = power(a, i, p);
int j = h.find(val) == h.end() ? -1 : h[val];
if (j >= 0 && i * t - j >= 0) return i * t - j;
}
return -1;
}
代码视为加入了 #define int long long
。
坑点
若
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】