卡特兰数、Prüfer 序列、BSGS
1 卡特兰数
1.1 概述
卡特兰数的前几项是
卡特兰数在组合数学中有着许多应用。下面给出一个经典例子:
在网格中向右或向上走,从
走到 ,并且不能越过对角线的路径条数。
该问题的结果就是卡特兰数,记为
1.2 通项公式
卡特兰数的通项公式有很多,下面给出几个常用公式:
- (递归定义)
。 - (递推公式)
。 - (通项公式)
。
1.3 应用
卡特兰数的特征是:一种操作数量时刻不能超过另一种操作数量,或者两种操作不能有交集。这样的方案数一般是卡特兰数。
下面举几个经典例子:
- 在网格中向右或向上走,从
走到 ,并且不能越过对角线的路径条数。 个元素以此进栈,合法的出栈序列。 对括号的合法匹配方案。 个节点构成的二叉树的形态数。- 在圆上选择
个点,将这些点成对连接起来使得所得到的 条线段不相交的方案数。 - 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方案数。
2 Prüfer 序列
2.1 概念
Prüfer 序列是一种将一颗有标号树用唯一一个序列表示的方法。
Prüfer 序列是这样构造的:每次选择一个标号较小的叶子结点并将其删去,同时在序列中记录它所连接的那个节点。重复
根据分析可以直接得到一段
下面我们讲解其优化以及其他应用。
2.2 线性构造
Prüfer 序列有一种
- 维护一个指针
指向编号最小的叶子结点。 - 删除
指向的节点,并检查是否有新的叶节点产生。 - 如果产生,则比较新节点编号与
的关系,如果小于 就将该节点删除,检查其是否产生新叶子结点。重复上述步骤直到出现新节点编号大于 。 - 让
增加,直到遇到一个新的叶子结点。
2.3 Prüfer 序列重建树
2.3.1 非线性解法
首先我们需要知道一条性质:每个结点在 Prüfer 序列中出现的次数是其度数减
然后我们就能得到标号最小的叶子节点编号,与当前枚举到的序列的点连接,同时减掉两个点的度。最后我们会剩下两个点,将他们连接即可。
这还是一个
2.3.2 线性解法
发现两者的非线性解法出乎意料的一致,那么线性解法也是一致的。在此不再赘述。
2.4 Cayley 公式
十分简洁的定理。如何证明呢?考虑 Prüfer 序列。任意一个长度为
3 BSGS
3.1 概述
BSGS(baby step giant step),又叫大步小步算法。用于求解离散对数(即模意义下对数)的算法。也就是已知
下面将一步步讲解 BSGS 算法。
3.2 算法内容
3.2.1 暴力法
既然
也就是说,
3.2.2 暴力优化
我们继续延续上面暴力法的思想。将
一般取
3.2.3 代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int Maxn = 2e5 + 5;
unordered_map <int, int> hs;
void BSGS(int a, int b, int m) {
int cur = 1, t = sqrt(m) + 1;
for(int B = 1; B <= t; B++) {
cur = cur * a % m;
hs[cur * b % m] = B;
}
int now = cur;
for(int A = 1; A <= t; A++) {
auto it = hs.find(now);
if(it != hs.end()) {
cout << A * t - it->second;
return ;
}
now = now * cur % m;
}
cout << "no solution";
return ;
}
int p, b, n;
signed main() {
ios::sync_with_stdio(0);
cin >> p >> b >> n;
BSGS(b, n, p);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律