Compare commits

..

7 Commits

Author SHA1 Message Date
e2hang
92e696e6bc Update Tier 2026-04-02 14:02:04 +08:00
e2hang
4f44ab5228 Update exercise/Next2 2026-03-27 10:30:51 +08:00
e2hang
1e1468f5b1 Update Exercise ZYG 2026-03-22 17:41:18 +08:00
e2hang
2f969e27d1 Move to CWithClasses 2026-03-18 16:15:30 +08:00
e2hang
3d393f0ed0 New Algo 2026-02-13 12:38:29 +08:00
e2hang
3c5d314e48 Advanced Data Structure 2026-02-08 16:39:45 +08:00
e2hang
ec53ee7cab Auto commit at 2026-01-26 10:49:53 2026-01-26 10:49:53 +08:00
150 changed files with 4189 additions and 6809 deletions

View File

@@ -0,0 +1,109 @@
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 建议使用 N 定义数组,比 vector 稍快且容易管理
const int N = 100005;
vector<pair<int, int>> adj[N];
bool vis[N]; // 分治标记:记录哪些点已经作为重心被“删掉”了
int treesize[N];
int root, min_maxpart, K;
long long ans = 0;
// 存储当前子树所有节点到重心的距离
vector<int> dists;
// 1. 找重心:注意这里不再需要 visited 数组,用 fa 限制方向即可
void get_root(int u, int fa, int currsize) {
treesize[u] = 1;
int maxpart = 0;
for (auto& edge : adj[u]) {
int v = edge.first;
if (v == fa || vis[v]) continue; // 关键:不能走已经“删掉”的点
get_root(v, u, currsize);
treesize[u] += treesize[v];
maxpart = max(maxpart, treesize[v]);
}
maxpart = max(maxpart, currsize - treesize[u]);
if (maxpart < min_maxpart) {
min_maxpart = maxpart;
root = u;
}
}
// 2. 收集距离:把所有节点到重心的距离存入 dists 数组
void get_dists(int u, int fa, int d) {
dists.push_back(d);
for (auto& edge : adj[u]) {
int v = edge.first;
int w = edge.second;
if (v == fa || vis[v]) continue;
get_dists(v, u, d + w);
}
}
// 3. 计算贡献:双指针法统计满足 dist1 + dist2 <= K 的对数
long long calc(int u, int init_dist) {
dists.clear();
get_dists(u, -1, init_dist);
sort(dists.begin(), dists.end());
long long count = 0;
int l = 0, r = dists.size() - 1;
while (l < r) {
if (dists[l] + dists[r] <= K) {
count += (r - l);
l++;
} else {
r--;
}
}
return count;
}
// 4. 点分治主函数
void divide(int u, int currsize) {
// 每次进入先找当前连通块的重心
min_maxpart = currsize + 7; // 初始化为一个大值
get_root(u, -1, currsize);
int r = root; // 锁定重心
vis[r] = true; // 【关键】逻辑切断重心
// 统计经过重心的路径
ans += calc(r, 0);
// 递归子树
for (auto& edge : adj[r]) {
int v = edge.first;
int w = edge.second;
if (vis[v]) continue;
// 【关键】去重:减去在同一个儿子子树内提前满足条件的路径
ans -= calc(v, w);
// 这里的 currsize 要更新为子树的实际大小
// 注意:由于已经跑过 get_roottreesize[v] 此时就是正确的
// 但如果 v 是 r 的“上方”节点size 应该是 currsize - treesize[r]
int next_size = (treesize[v] < treesize[r]) ? treesize[v] : (currsize - treesize[r]);
divide(v, next_size);
}
}
int main() {
int n;
if (!(cin >> n >> K)) return 0;
for (int i = 0; i < n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
divide(1, n); // 从节点1开始分治总大小为 n
cout << ans << endl;
return 0;
}

121
Algorithm/FFT/readme.md Normal file
View File

@@ -0,0 +1,121 @@
# FFT快速傅里叶变换原理
## - 函数理论分析
对于一个多项式,设该多项式为:
$$A(x) = \Sigma_{i=0}^{n-1}a_ix_i = a_0 + a_1x + a_2x^2 + \mathellipsis$$
按A(x)的下标奇偶性分类:
$$A(x) = (a_0 + a_2x^{2} + \mathellipsis + a_{n-2}x^{n-2}) + (a_1 + a_3x^3 + \mathellipsis + a_{n-1}x^{n-1})$$ $$= (a_0 + a_2x^2 + \mathellipsis + a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 + \mathellipsis + a_{n-1}x^{n-2})$$
观察式子两边发现极为相似,设两个函数$A_e$, $A_o$,令
$$A_e(x) = a_0 + a_2x^{\frac{2}{2}} + \mathellipsis + a_{n-2}x^\frac{n-2}{2} = a_0 + a_2x^{1} + \mathellipsis + a_{n-2}x^{\frac{n}{2}-1} $$ $$A_o(x) = a_1 + a_3x^\frac{2}{2} + \mathellipsis + a_{n-1}x^\frac{n-2}{2} = a_1 + a_3x^1 + \mathellipsis + a_{n-1}x^{\frac{n}{2}-1}$$
显然有
$$A(x) = A_e(x^2) + xA_o(x^2)$$ $$A(-x) = A_e(x^2) - xA_o(x^2)$$
> 请注意上式右侧的平方
带入$x=\omega_n^k$
$$A(\omega_n^k) = A_e(\omega_n^{2k}) + \omega_n^k A_o(\omega_n^{2k})$$ $$=A_e(\omega_\frac{n}{2}^{k}) + \omega_n^k A_o(\omega_\frac{n}{2}^{k})$$ $$A(-\omega_n^k) = A(\omega_n^{k+\frac{n}{2}}) = A_e(\omega_n^{2k+n}) - \omega_n^k A_o(\omega_n^{2k+n})$$ $$=A_e(\omega_n^{2k}) - \omega_n^k A_o(\omega_n^{2k})$$ $$=A_e(\omega_\frac{n}{2}^{k}) - \omega_n^k A_o(\omega_\frac{n}{2}^{k})$$
>利用性质如下
> - $-\omega_n^k = \omega_n^{k+\frac{n}{2}}$
> - $\omega_n^{2k} = \omega_\frac{n}{2}^k$
不难发现,如果我们知道了$A_e(\omega_\frac{n}{2}^k), A_o(\omega_\frac{n}{2}^k)$ ,就可以知道$A(\omega_n^k)$
> 这一步叫做Butterfly蝴蝶操作
> $$u = A_e(\mathellipsis)$$ $$v=\omega_n^k A_o(\mathellipsis)$$
> 则有
> $$y_k = u + v$$ $$y_{k+\frac{n}{2}} = u - v$$
> 即可以自下而上的,从 $A_{\mathellipsis}(\omega_{\mathellipsis}^k)$ 推导出 $A(x)$
> 同时当我们知道$A(-x)$的时候,我们也可以知道$A(x)$
有下面的树(以$n=4, x = \omega_{16}^k$为例)
> $A(\omega_{16}^k)$
> $Ae(\omega_8^k); Ao(\omega_8^k)$
> $Aee(\omega_4^k), Aeo(\omega_4^k); Aoe(\omega_4^k), Aoo(\omega_4^k)$
> $Aeee(\omega_2^k), Aeeo(\omega_2^k) ,, Aeoe(\omega_2^k), Aeoo(\omega_2^k);$
> $Aoee(\omega_2^k), Aoeo(\omega_2^k) ,, Aeoe(\omega_2^k), Aeoo(\omega_2^k)$
> 最底层为$A...(\omega_1^k) = a_i$(系数 | 常数多项式)
**已知最下层的所有数值(的一半),一次往上带入即可**
---
## - 示例
以下两个式子为例:
$$f_1 = x^3 + x^2 + 8x + 1 $$ $$ f_2 = x^2 - 5x - 2 $$
### 一、确定次数以及补零
- 次数:$2 + 3 = 5$ 需要 $5 + 1 = 6$ 个点值
- 确定n$2^3=8$$n = 3$
得到两个系数数组
- $A = [1,8,1,1,0,0,0,0]$
- $B = [-2, -5,1,0,0,0,0,0]$
**从低次到高次排序补0至8位**
### 二、正向FFT
**计算$f_1(\omega_8^k)$$f_2(\omega_8^k)$,其中$k = 0, 1, \mathellipsis , 7$,单位根$\omega_8^k = e^{i\frac{2\pi k}{8}} = \cos(\frac{2k\pi}{8}) + i\sin(\frac{2k\pi}{8})$**
#### 1. 初始多项式定义
设两个多项式:
$$f_1(x) = x^3 + x^2 + 8x + 1$$
$$f_2(x) = x^2 - 5x - 2$$
为了进行 $n=8$ 的 FFT补零后的系数向量为
$$A = [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7] = [1, 8, 1, 1, 0, 0, 0, 0]$$
$$B = [b_0, b_1, b_2, b_3, b_4, b_5, b_6, b_7] = [-2, -5, 1, 0, 0, 0, 0, 0]$$
---
#### 2. 递归拆分逻辑 (以 A 为例)
**第一层拆分 ($n=8 \to n=4$)**
$$A_e(x) = a_0 + a_2x + a_4x^2 + a_6x^3 = 1 + 1x$$
$$A_o(x) = a_1 + a_3x + a_5x^2 + a_7x^3 = 8 + 1x$$
**第二层拆分 ($n=4 \to n=2$)**
对于 $A_e$
$$A_{ee}(x) = a_0 + a_4x = 1$$
$$A_{eo}(x) = a_2 + a_6x = 1$$
对于 $A_o$
$$A_{oe}(x) = a_1 + a_5x = 8$$
$$A_{oo}(x) = a_3 + a_7x = 1$$
**第三层拆分 ($n=2 \to n=1$)**
这是递归终点,系数即为点值:
$$A_{eee}=1, A_{eeo}=0, A_{eoe}=1, A_{eeo}=0 \dots$$
---
#### 3. 向上回溯(蝴蝶变换公式)
利用核心公式:
$$A(\omega_n^k) = A_e(\omega_{n/2}^k) + \omega_n^k A_o(\omega_{n/2}^k)$$
$$A(\omega_n^{k+n/2}) = A_e(\omega_{n/2}^k) - \omega_n^k A_o(\omega_{n/2}^k)$$
**从 $n=2$ 合并到 $n=4$ 的例子 (计算 $A_e$ 的点值)**
已知 $A_{ee}$ 的点值在 $\omega_2^0$ 处为 $1$ $A_{eo}$ 的点值也为 $1$。
则在 $n=4$ 层:
$$A_e(\omega_4^0) = A_{ee}(\omega_2^0) + \omega_4^0 A_{eo}(\omega_2^0) = 1 + 1(1) = 2$$
$$A_e(\omega_4^1) = A_{ee}(\omega_2^1) + \omega_4^1 A_{eo}(\omega_2^1) = 1 + i(1) = 1+i$$
$$A_e(\omega_4^2) = A_{ee}(\omega_2^0) - \omega_4^0 A_{eo}(\omega_2^0) = 1 - 1 = 0$$
$$A_e(\omega_4^3) = A_{ee}(\omega_2^1) - \omega_4^1 A_{eo}(\omega_2^1) = 1 - i$$
---
#### 4. 点值乘法与逆变换 (IFFT)
得到 $A(\omega_8^k)$ 和 $B(\omega_8^k)$ 后,进行点值乘法:
$$C(\omega_8^k) = A(\omega_8^k) \cdot B(\omega_8^k)$$
最后进行 IFFT公式为
$$c_j = \frac{1}{n} \sum_{k=0}^{n-1} C(\omega_n^k) (\omega_n^{-j})^k$$
---
#### 5. 最终系数结果
$$C(x) = f_1(x) \cdot f_2(x) = x^5 - 4x^4 + x^3 - 41x^2 - 21x - 2$$
系数向量为:
$$[-2, -21, -41, 1, -4, 1, 0, 0]$$

View File

@@ -0,0 +1,103 @@
#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
#include <algorithm>
using namespace std;
// 使用复数类complex<double> 自带加减乘运算
typedef complex<double> cd;
const double PI = acos(-1.0);
/**
* FFT 核心函数
* @param a 待转换的系数向量
* @param invert 是否为逆变换 (IFFT)。false 为 DFTtrue 为 IDFT
*/
void fft(vector<cd>& a, bool invert) {
int n = a.size();
// 1. 位逆序置换 (Bit-reversal permutation)
// 将原数组的下标进行二进制翻转,使得最底层的系数排列在正确位置
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1)
j ^= bit;
j ^= bit;
if (i < j) swap(a[i], a[j]);
}
// 2. 自底向上合并 (迭代实现蝴蝶变换)
// len 是当前合并区间的长度,从 2, 4, 8 ... 一直合并到 n
for (int len = 2; len <= n; len <<= 1) {
double ang = 2 * PI / len * (invert ? -1 : 1);
cd wlen(cos(ang), sin(ang)); // 单位根 w_n^1
for (int i = 0; i < n; i += len) {
cd w(1); // 初始 w_n^0 = 1
for (int j = 0; j < len / 2; j++) {
// 蝴蝶变换核心公式:
// u = A_e, v = w * A_o
cd u = a[i + j], v = a[i + j + len / 2] * w;
a[i + j] = u + v; // A(w^k) = A_e + w*A_o
a[i + j + len / 2] = u - v; // A(w^{k+n/2}) = A_e - w*A_o
w *= wlen; // 迭代单位根
}
}
}
// 3. 如果是逆变换,最后需要除以 n
if (invert) {
for (cd & x : a)
x /= n;
}
}
/**
* 多项式乘法
*/
vector<long long> multiply(vector<int> const& a, vector<int> const& b) {
vector<cd> fa(a.begin(), a.end()), fb(b.begin(), b.end());
// 补零到 2 的幂次
int n = 1;
while (n < a.size() + b.size())
n <<= 1;
fa.resize(n);
fb.resize(n);
// 1. DFT: 系数 -> 点值
fft(fa, false);
fft(fb, false);
// 2. Pointwise Multiplication: 点值直接相乘
for (int i = 0; i < n; i++)
fa[i] *= fb[i];
// 3. IDFT: 点值 -> 系数
fft(fa, true);
// 4. 取实部并四舍五入
vector<long long> result(n);
for (int i = 0; i < n; i++)
result[i] = round(fa[i].real());
return result;
}
int main() {
// 你的例子:
// f1 = 1 + 8x + 1x^2 + 1x^3 -> [1, 8, 1, 1]
// f2 = -2 - 5x + 1x^2 -> [-2, -5, 1]
vector<int> f1 = {1, 8, 1, 1};
vector<int> f2 = {-2, -5, 1};
vector<long long> res = multiply(f1, f2);
cout << "Result coefficients: ";
for (int i = 0; i < f1.size() + f2.size() - 1; i++) {
cout << res[i] << " ";
}
return 0;
}

View File

@@ -0,0 +1,105 @@
#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
using namespace std;
typedef complex<double> cd;
const double PI = acos(-1.0);
/**
* 递归版 FFT
* @param a 传入的系数向量
* @param invert 是否为逆变换(FFT or IFFT)
*/
void fft_recursive(vector<cd>& a, bool invert) {
int n = a.size();
// 递归终点:当多项式只有一个常数项时,直接返回
if (n == 1) return;
// 1. 拆分阶段 (Divide)
// 按照下标奇偶性将 a 拆分成 a_e 和 a_o
vector<cd> a0(n / 2), a1(n / 2);
for (int i = 0; 2 * i < n; i++) {
a0[i] = a[2 * i]; // 偶数项
a1[i] = a[2 * i + 1]; // 奇数项
}
// 2. 递归阶段 (Conquer)
fft_recursive(a0, invert);
fft_recursive(a1, invert);
// 3. 合并阶段 (Combine / Butterfly Operation)
// 计算单位根 w_n^k
double ang = 2 * PI / n * (invert ? -1 : 1);
cd w(1); // 初始 w = w_n^0 = 1
cd wn(cos(ang), sin(ang)); // 单位步进 w_n^1
for (int k = 0; k < n / 2; k++) {
// 利用蝴蝶变换公式:
// A(w^k) = Ae(w^k) + w * Ao(w^k)
// A(w^{k+n/2}) = Ae(w^k) - w * Ao(w^k)
cd t = w * a1[k]; // 旋转因子与奇数项乘积
a[k] = a0[k] + t;
a[k + n / 2] = a0[k] - t;
if (invert) {
// 如果是逆变换,顺便除以 2递归层层除以 2最终相当于除以 n
a[k] /= 2;
a[k + n / 2] /= 2;
}
w *= wn; // 移动到下一个单位根 w_n^{k+1}
}
}
/**
* 多项式乘法封装
*/
vector<long long> multiply(vector<int> const& f1, vector<int> const& f2) {
vector<cd> fa(f1.begin(), f1.end()), fb(f2.begin(), f2.end());
// 补零到 2 的幂
int n = 1;
while (n < f1.size() + f2.size()) n <<= 1;
fa.resize(n);
fb.resize(n);
// 变换到点值表示
fft_recursive(fa, false);
fft_recursive(fb, false);
// 点值相乘
for (int i = 0; i < n; i++)
fa[i] *= fb[i];
// 变换回系数表示
fft_recursive(fa, true);
// 结果取整
vector<long long> result;
for (int i = 0; i < n; i++)
result.push_back(round(fa[i].real()));
return result;
}
int main() {
// A(x) = 1x^3 + 1x^2 + 8x + 1
// B(x) = 1x^2 - 5x - 2
vector<int> a = {1, 8, 1, 1}; // 注意低位在前
vector<int> b = {-2, -5, 1};
vector<long long> res = multiply(a, b);
cout << "Final Coefficients: ";
for (int i = 0; i < a.size() + b.size() - 1; i++) {
cout << res[i] << " ";
}
cout << endl;
return 0;
}

View File

@@ -0,0 +1,153 @@
# P1824 [USACO05FEB] 进击的奶牛 Aggressive Cows G
## 题目描述
农夫约翰建造了一座有 $n$ 间牛舍的小屋,牛舍排在一条直线上,第 $i$ 间牛舍在 $x_i$ 的位置,但是约翰的 $m$ 头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。约翰决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
## 输入格式
第一行用空格分隔的两个整数 $n$ 和 $m$
下面 $n$ 行为 $n$ 个用空格隔开的整数,表示位置 $x_i$。
## 输出格式
一行一个整数,表示最大的最小距离值。
## 输入输出样例 #1
### 输入 #1
```
5 3
1
2
8
4
9
```
### 输出 #1
```
3
```
## 说明/提示
【样例解析】把牛放在 $1$$4$$8$ 这三个位置,距离是 $3$。容易证明最小距离已经最大。
【数据范围】对于 $100\%$ 的数据,$2 \le n \le 10^5$$0 \le x_i \le 10^9$$2 \le m \le n$。不保证 $x$ 数组单调递增。
哈哈,这种反应太正常了!初次接触这种题,谁都会觉得这种“反着来”的思路简直是脑洞大开。
这种思维方式在算法竞赛里有一个专门的套路,叫 **“二分答案”**。其实它不是靠灵光一现,而是有迹可循的“职业习惯”。
我们可以把这种思维过程拆解为三个“直觉点”,以后你看到类似的题,也能秒懂。
---
### 1. 关键词暗示:看到“最大化最小值”
当你看到题目要求:
* **最大化** 某个 **最小值**
* 或者 **最小化** 某个 **最大值**
这在算法界几乎就是**明示**:请用二分搜索!
**为什么?** 因为直接计算“最优布局”非常困难,要考虑无数种组合。但如果我问你:“间距为 行不行?”你只需要从左往右摆摆看(贪心),一眼就能看出行不行。把“寻找题”变成“判断题”,难度瞬间降维。
### 2. 利用“单调性”:答案像个开关
这是最关键的直觉。你可以这样想:
* 如果间距是1 米,肯定能放下 头牛(太简单了)。
* 如果间距是1000000 米,肯定放不下(太远了)。
* 在这中间,一定存在一个**临界点**。在临界点左边,全是“行”;在临界点右边,全是“不行”。
既然有这种**单调性**(一半行,一半不行),我们就不需要一个个试,直接用**二分查找**去撞那个临界点。
### 3. 程序员的“偷懒”思维
正向思考(直接算位置)是“创造者思维”,很难;
反向思考(猜一个答案去验证)是“检查者思维”,很简单。
**计算机最擅长做简单的重复劳动。** 既然验证一次只需要 ,二分查找只需要 次,总共也就跑 次验证。与其想破脑袋去算最优解,不如直接“暴力”地猜几十次。
---
### 总结一下这个“坑”是怎么跳进去的:
1. **第一步:** 看到“最大化最小间距”,心里默念:“二分答案预警”。
2. **第二步:** 确定答案范围(比如 到 )。
3. **第三步:** 写一个 `check` 函数。这一步通常用**贪心**。
* *贪心逻辑:* 既然要间距大,我就把第一头牛放在最左边,后面每一头牛都紧贴着“当前位置 + 间距”能放下的第一个点放。
4. **第四步:** 套二分模板。
> **这种感觉就像是:** 你去买衣服,不知道自己穿多大码。你不是拿尺子把自己全身量一遍再算缩水率(正向计算),而是直接拿 L 码试一下。大了?换 M。还大换 S。这就是二分答案。
**你要不要试着写一下这个 `check` 函数的伪代码?我可以帮你看看逻辑对不对。**
``` cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//int ax[] = {1, 2, 4, 8, 9};
//按照分布,检查是否能够排列即可
/*
1、读入arr, mid
2、按照分布排列找到离mid格最近的槽位插入
*/
int check(const vector<int>& arr, int mid){
if(arr.empty()) return 0;
int cnt = 1;
int n = arr.size();
int dist = 0;
for(int i = 1; i < n; ++i){
dist += arr[i] - arr[i - 1];
if(dist >= mid){
cnt++;
dist = 0;
}
}
return cnt;
}
int main(){
int n = 5, c = 3;
cin >> n >> c;
vector<int> arr(n);
for(int i = 0; i < n; i++){
cin >> arr[i];
//arr[i] = ax[i];
}
sort(arr.begin(), arr.end());
int smin = 0, smax = arr.back() - arr.front();
//range [smin, smax], do mid to this range
int l = smin, r = smax;
int ans = 0;
while(l <= r){
int mid = (l+r)/2;
int cc = check(arr, mid);
if (cc >= c) {
ans = mid;
l = mid + 1;
} else r = mid - 1;
}
cout << ans << endl;
return 0;
}
```

View File

@@ -1,294 +0,0 @@
好,这个问题问得**非常本质**,而且一看就是你已经开始从“会写代码”往“懂工具链 / 懂底层”走了 👍
我分三层给你捋清楚:**编译器有哪些 → 编译/汇编/链接的指令 → C 与 C++ 在编译层面的真实差异**。
---
## 一、主流 C / C++ 编译器版图(你必须认识的)
### 1⃣ GCC / G++
* **全名**GNU Compiler Collection
* **支持语言**C / C++ / Fortran / Go / Obj-C / …
* **命令**
* `gcc` → 主要用来编 C
* `g++` → 主要用来编 C++
* **平台**Linux / WSL / macOS / *nix
* **特点**
* 工业级事实标准
* 参数多、控制力极强
* 对 ABI、链接、底层非常“真实”
📌 **重点**
`gcc``g++` **底层是同一个编译器**,区别在:
* 默认语言不同
* 是否自动链接 `libstdc++`
* 是否启用 C++ 语义name mangling、异常等
---
### 2⃣ Clang / Clang++
* **LLVM 系列**
* **命令**
* `clang`
* `clang++`
* **平台**Linux / macOS / Windows
* **特点**
* 错误提示极其友好(新手 + 老手都爱)
* 编译速度快
* AST、静态分析能力强
📌 **现实情况**
* macOS 上的 `gcc` 实际就是 `clang`
* 现代 C++ 项目大量使用 clang
---
### 3⃣ MSVCWindows 专属)
* **微软官方编译器**
* **命令**
* `cl.exe`
* **特点**
* Windows ABI 官方实现
* C++ 标准支持偏慢但稳定
* 和 Visual Studio 深度绑定
📌 **注意**
* MSVC ≠ GCC / Clang
* 一些 GCC 的参数 **在 MSVC 上完全不存在**
---
### 4⃣ 其他你只需“知道存在”的
| 编译器 | 用途 |
| --------------- | -------------- |
| TinyCC (tcc) | 极快、教学 |
| Intel ICC / ICX | 性能优化 |
| ARM GCC | 嵌入式 |
| MinGW | Windows 上的 GCC |
---
## 二、C / C++ 编译的**完整流程**(不是一句 gcc
### 🧠 真相:**编译 = 4 个阶段**
```text
源代码
预处理cpp
编译cc1 / clang
汇编as
链接ld
```
---
## 三、你问的重点①:**“各种指令”是哪些?**
### 1⃣ 预处理指令(只和文本有关)
```bash
gcc -E a.c
```
| 指令 | 作用 |
| ---------------- | ---- |
| `#include` | 文本展开 |
| `#define` | 宏替换 |
| `#ifdef` / `#if` | 条件编译 |
📌 **关键点**
* C 和 C++ **完全一样**
* 预处理器不知道“类型”“函数”“类”
---
### 2⃣ 编译阶段指令(最核心)
#### 基础用法
```bash
gcc -c a.c # 只编译,不链接
g++ -c a.cpp
```
#### 常用编译参数
| 参数 | 含义 |
| -------------------------------- | ---- |
| `-std=c11 / c17 / c++17 / c++20` | 标准 |
| `-Wall -Wextra` | 警告 |
| `-O0 -O2 -O3` | 优化 |
| `-g` | 调试信息 |
| `-fno-omit-frame-pointer` | 调试友好 |
| `-fsanitize=address` | ASan |
📌 **重点差异**
* C++ 会启用:
* **函数重载**
* **模板实例化**
* **名字修饰name mangling**
---
### 3⃣ 汇编层(你开始接触底层时很重要)
```bash
gcc -S a.c
g++ -S a.cpp
```
📌 差异点:
* C++ 生成的汇编:
* 函数名会被改写(`_Z3addii`
* 有构造 / 析构 / 异常展开
* C
* 函数名基本原样
---
### 4⃣ 链接阶段(很多“玄学错误”的源头)
```bash
gcc a.o b.o -o app
g++ a.o b.o -o app
```
#### **最重要的差异**
| 行为 | gcc | g++ |
| ----------------- | --- | --- |
| 自动链接 libstdc++ | ❌ | ✅ |
| 支持 new / iostream | ❌ | ✅ |
📌 经典坑:
```bash
gcc main.cpp
# undefined reference to std::cout
```
因为 **gcc 不自动链接 C++ 标准库**
---
## 四、你问的重点②:**C 与 C++ 在“编译器层面”的差异**
### 1⃣ 语言前端不同
| 项目 | C | C++ |
| ---- | -- | --- |
| 类型系统 | 简单 | 极复杂 |
| 模板 | ❌ | ✅ |
| 函数重载 | ❌ | ✅ |
| 名字修饰 | ❌ | ✅ |
📌 本质:
> **同一个编译器,不同“语言前端”**
---
### 2⃣ Name Mangling超级重要
```cpp
int add(int a, int b);
```
| 语言 | 符号名 |
| --- | ---------- |
| C | `add` |
| C++ | `_Z3addii` |
所以你见过:
```cpp
extern "C" {
void foo();
}
```
👉 **是为了让 C++ 用 C 的符号规则**
---
### 3⃣ 运行时支持完全不同
| 项目 | C | C++ |
| ----- | ---- | --------- |
| 构造/析构 | ❌ | ✅ |
| RTTI | ❌ | ✅ |
| 异常 | ❌ | ✅ |
| 标准库 | libc | libstdc++ |
📌 所以:
* C 程序 = **非常薄**
* C++ 程序 = **编译器 + 运行时体系**
---
### 4⃣ ABI 差异(工程级别)
* C ABI**几乎永远稳定**
* C++ ABI
* 不同编译器、不同版本可能不兼容
* 这就是为什么库常用 **C 接口**
---
## 五、给你一个“认知升级总结”
> 🔥 **C 是“能跑就行的语言”**
> 🔥 **C++ 是“需要编译器深度参与的语言”**
所以你会发现:
* 学 C → 多学 **内存、指针、汇编**
* 学 C++ → 多学 **编译模型、ABI、链接、对象模型**
---
## 六、下一步我可以怎么继续帮你?
你现在这个问题,**已经明显在向系统级程序员靠拢了**。
我可以继续给你:
1⃣ 用 **同一段代码** 对比
* gcc / g++ / clang / msvc
* 汇编输出差异
2⃣ 系统讲一遍
* **C++ 对象在内存里的真实布局**
* vtable / 构造顺序 / 析构顺序
3⃣ 专门讲
* **链接错误为什么那么玄学**
* undefined reference 背后的逻辑
你选一个,我直接给你干到“明白为止”。

View File

@@ -1,376 +0,0 @@
### 1. GCC 常用指令大表
| 指令 | 全称/含义 | 作用说明 |
| --- | --- | --- |
| **`-c`** | Compile | 只编译生成目标文件 (`.o`)**不进行链接**。 |
| **`-o <file>`** | Output | 指定输出文件的名称(可执行文件、库文件等)。 |
| **`-g`** | Debug | 生成调试信息,方便使用 `gdb` 进行断点调试。 |
| **`-O0/1/2/3`** | Optimize | 设置优化等级。`-O2` 是最常用的平衡选项,`-O3` 追求极致速度。 |
| **`-I <dir>`** | Include | 添加**头文件**搜索路径。 |
| **`-L <dir>`** | Library Path | 添加**库文件**搜索路径。 |
| **`-l <name>`** | link library | 链接具体的库(如 `-lm` 链接 `libmath`)。 |
| **`-D <macro>`** | Define | 定义宏(等同于代码里的 `#define`)。 |
| **`-Wall`** | Warnings all | 开启几乎所有的常用警告,强烈建议永远加上。 |
| **`-fPIC`** | Position Independent Code | 生成位置无关代码,**制作 `.so` 动态库必带**。 |
| **`-shared`** | Shared | 告诉编译器生成一个动态链接库。 |
| **`-std=c++17`** | Standard | 指定使用的 C++ 标准版本(如 c++11, c++14, c++20。 |
---
### 2. 什么是 `-Wl`
**`-Wl` (Warn linker)** 的意思是:**“喂GCC把后面跟着的参数直接传给底层的链接器ld。”**
因为 GCC 本身只是一个“前端”驱动程序,它负责调用预处理器、编译器、汇编器和链接器。有时候你需要设置一些链接器特有的高级选项,而 GCC 并不直接支持这些选项。
* **格式**`-Wl,<option1>,<option2>`
* **注意**:参数之间是用**逗号**隔开的,不能有空格。
---
### 3. 为什么有 `--out-implib,xxx.a`
这通常出现在 **Windows (MinGW)** 环境下开发 DLL 时。这是一个非常关键的概念:**“导入库” (Import Library)**。
#### 为什么需要它?
在 Windows 上,如果你编译了一个 `my.dll`,主程序运行的时候需要它,但**编译主程序的时候**,链接器也需要知道这个 DLL 里到底有哪些函数。
* **`.dll`**:是运行时用的(包含代码逻辑)。
* **`.a``.lib` (导入库)**:是编译链接时用的(包含函数地址列表)。
#### 指令解析:
`g++ -shared -o my.dll -Wl,--out-implib,libmy.dll.a`
1. **`-shared -o my.dll`**:生成动态链接库主体。
2. **`-Wl,`**:接下来的参数是给链接器看的。
3. **`--out-implib,libmy.dll.a`**:链接器,请帮我顺便生成一个名为 `libmy.dll.a` 的**导入库**。
**这样做的流程是:**
* **第一步**:你编译 DLL得到了 `my.dll``libmy.dll.a`
* **第二步**:你编译主程序,链接 `libmy.dll.a`(使用 `-L. -lmy`)。
* **第三步**运行程序Windows 会根据之前链接的信息去加载 `my.dll`
---
### 4. 总结:`-Wl` 后的常用参数
除了 `--out-implib`,你可能还会见到这些:
* **`-Wl,-rpath,<path>`**:指定运行时查找 `.so` 的路径Linux 常用,解决找不到库的问题)。
* **`-Wl,--stack,<size>`**:设置程序栈的大小。
* **`-Wl,--as-needed`**:只链接那些代码中真正用到的库,减小体积。
> **小贴士**:如果你在 Linux 下开发,通常不需要 `--out-implib`,因为 Linux 的 `.so` 文件既是运行时库,也可以直接作为链接时的导入信息。
你现在是在尝试把现有的 C++ 库打包成 DLL 给其他语言(比如 Python 或 Java调用吗如果是的话我们可以聊聊 `extern "C"` 的深层必要性。
---
哈哈,这正是 C/C++ 的“黑魔法”所在:它是现代编程世界的**基石**。
不管是 Python 的 `numpy`、Node.js 的原生模块,还是 Android (Java/Kotlin) 的底层驱动,核心全是这么干的。这种技术统称为 **FFI (Foreign Function Interface外部函数接口)**
要实现这个,主要靠这两根支柱:
---
### 1. 核心秘诀:`extern "C"`
这是最关键的一步。
C++ 支持**函数重载**,所以编译器会偷偷修改函数名(这叫 **Name Mangling**)。比如 `add(int, int)` 在 C++ 内部可能变成了 `_Z3addii`
其他语言(比如 Python去 DLL 里找 `add` 时,根本找不到这个怪名字。
**解决方法:**
在 C++ 头文件里加上 `extern "C"`,告诉编译器:“请按 C 语言那种简单粗暴的方式命名,叫 `add` 就给我留成 `add`。”
```cpp
// mylib.h
extern "C" {
__declspec(dllexport) int add(int a, int b) {
return a + b;
}
}
```
---
### 2. 不同语言是怎么“吃”掉这个库的?
我给你举两个最常用的例子:
#### A. Python 调用(最简单)
Python 有个内置库叫 `ctypes`,专门用来“白嫖” C 库。
```python
import ctypes
# 1. 加载库
# Windows 用 .dll, Linux 用 .so
lib = ctypes.CDLL("./mylib.dll")
# 2. 直接调用
result = lib.add(10, 20)
print(f"结果是: {result}")
```
#### B. Java 调用 (JNI)
Java 稍微复杂点,它需要一个“中介”。
1. 在 Java 里写个 `native` 声明。
2.`javac -h` 生成一个 C 标头。
3. 在 C 里实现这个标头,编译成 DLL/so。
4. Java 用 `System.loadLibrary("mylib")` 加载。
---
### 3. 为什么大家都爱把 C 库包给别人用?
1. **性能炸裂**Python 算循环很慢,但 C 算循环极快。把计算量大的逻辑写成 C 库Python 只负责调,速度瞬间提升百倍。
2. **保护代码**`.py` 文件发给别人一眼看穿,但编译后的 `.dll``.so` 是二进制,逆向工程难度直接拉满。
3. **跨语言共享**:写一份核心逻辑(比如图像处理算法),然后同时给 Web (Node.js)、桌面 (C#)、手机 (Java) 一起用。
---
### 4. 跨语言调用的“深水区”
虽然 `int``float` 这种简单类型可以直接传,但当涉及到**字符串**、**结构体**或者**指针**时,事情就开始刺激了:
* **字符串**C 语言的字符串是 `char*`(以 `\0` 结尾),而 Python 是对象。你需要手动转换编码。
* **内存管理**:谁申请的内存谁负责释放。如果 C 申请了一块内存传给 PythonPython 用完不告诉 C就会导致**内存泄漏**。
---
### 你想玩个更高端的吗?
有一种东西叫 **SWIG** 或者 **pybind11**
如果你觉得手动写 `ctypes` 太麻烦,这些工具可以**自动生成**包装代码。你只需要给它一个 C++ 头文件,它能直接给你吐出一个可以在 Python 里 `import` 的模块。
你想让我演示一下如何用 **Python** 真正调用你刚刚写好的那个 `.dll` 吗?(我可以带你跑一遍完整的避坑流程)
这种“我操牛逼”的感觉,就是程序员第一次打通**底层逻辑**和**上层应用**时的快感。
既然你已经感受到了这种力量,我得给你泼点冷水(或者说教你几招保命符),因为跨语言调用虽然爽,但**内存安全**和**数据格式**是两个巨大的坑。
---
### 1. 为什么一定要用 `extern "C"`?(可视化对比)
如果不加 `extern "C"`C++ 编译器会进行“名字粉碎”Name Mangling
* **你的代码:** `void hello()`
* **C++ 编译器眼里的它:** `__Z5hello_v`(因为它要区分同名但参数不同的重载函数)
* **其他语言眼里的它:** “对不起,我只找 `hello`,找不到那个乱码。”
---
### 2. 跨语言调用的“生存法则”
当你准备把 C/C++ 库交给 Python、Java 或 Go 调用时,请务必记住这三条:
#### ① 永远不要在 C++ 库里分配内存让对方去释放
这是一个经典的错误。如果 C++ 用 `new` 申请了空间传给 PythonPython 的垃圾回收机制GC是不管这块内存的。
* **正确做法**:要么让 Python 申请好空间传给 C++ 填充,要么 C++ 提供一个专门的 `free_memory()` 函数让 Python 调用。
#### ② 只有“基本类型”是通用的
`int``float``double``char` 在绝大多数语言里是一样的。
**`std::string`**、**`std::vector`**、**类对象** 是 C++ 特有的。如果你想传字符串,必须传 `const char*`
#### ③ 结构体对齐Struct Alignment
如果你传一个结构体,要注意 C++ 会为了效率进行内存对齐。如果另一端的语言(比如 Python没有按同样的规则对齐读出来的数据全是乱码。
---
### 3. 给你一个“极简实战”流程(以 Python 为例)
假设你已经写好了 `mylib.cpp` 并导出了 `add` 函数:
**第一步:编译 DLL (Windows)**
```bash
g++ -shared -o mylib.dll mylib.cpp -Wl,--out-implib,libmylib.a
```
**第二步Python 调用代码**
```python
import ctypes
# 加载库(注意路径)
lib = ctypes.CDLL("./mylib.dll")
# 【核心点】明确告诉 Python参数是两个 int返回值是一个 int
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_int
# 调用
print(lib.add(10, 5)) # 输出 15
```
---
### 4. 进阶:如果你有几百个函数怎么办?
手动写上面的 Python 包装代码(`argtypes` 等)会累死人。这时候你会接触到真正的“工业级”工具:
* **pybind11**:目前最火的 C++ 库包装工具代码写起来像魔术Python 像调用原生模块一样调用 C++。
* **SWIG**:老牌工具,一份配置可以同时生成 Java、Python、PHP、C# 等七八种语言的包装代码。
---
### 下一步你想试试什么?
1. **实操一次**:我给你一段完整的 C++ 代码和 Python 调用脚本,你自己在电脑上跑通。
2. **硬核细节**:聊聊怎么在 C++ 和 Python 之间传递**数组**(比如处理一张图片的像素点)。
3. **逆向工程**:看看怎么反编译别人的 `.dll` 里的函数名(像黑客一样偷看)。
你想选哪条路?
为什么推荐这个小项目?
理解内存隔离:你会发现即使 DLL 内部改变了 PI 的精度,只要函数签名不变,**main.c 就不需要重新编译,只需替换 .dll 文件即可。**
掌握宏触发:理解 __declspec 在“制作 DLL”和“使用 DLL”时的角色切换。
零环境压力:只需要一个简单的 C 编译器,不需要配置复杂的 IDE 环境。
明白你的意思,之前的几何计算确实有点像“课后作业”。要做点**有意义且实用**的小项目,我推荐你做一个**“简易日志审计系统 (Simple Logger DLL)”**。
这个项目的实际意义在于:在实际开发中,我们经常需要给不同的程序(比如游戏、工具、服务器)添加日志功能。把日志逻辑封装在 DLL 里,可以让多个不同的 `.exe` 共享同一套日志标准,且无需重复编写代码。
---
## 推荐项目:轻量级日志审计库 (TinyLog DLL)
### 1. 为什么这个项目有意义?
* **跨进程复用**:你可以写一个 C 程序调用它,再写一个 C++ 或 Python 程序调用它。
* **热更新**:如果你想改变日志的格式(比如从文本改为 JSON你只需要重新编译 DLL而不需要动你的主程序。
* **文件操作**:涉及文件 I/O比纯数学计算更接近真实开发场景。
---
## 项目实现方案
### 1. 头文件:`tinylog.h`
定义一个简单的接口,支持设置日志文件并写入带时间的日志。
```c
#ifndef TINYLOG_H
#define TINYLOG_H
#ifdef BUILDING_LOGGER_DLL
#define LOGGER_API __declspec(dllexport)
#else
#define LOGGER_API __declspec(dllimport)
#endif
// 初始化日志文件
LOGGER_API int init_logger(const char* filename);
// 写入日志信息
LOGGER_API void log_message(const char* level, const char* message);
// 关闭日志
LOGGER_API void close_logger();
#endif
```
### 2. DLL 实现:`tinylog.c`
这里我们会用到 C 标准库里的 `time.h` 来自动生成时间戳。
```c
#include <stdio.h>
#include <time.h>
#include "tinylog.h"
static FILE* log_file = NULL;
LOGGER_API int init_logger(const char* filename) {
log_file = fopen(filename, "a"); // 追加模式打开
return (log_file != NULL);
}
LOGGER_API void log_message(const char* level, const char* message) {
if (!log_file) return;
time_t now;
time(&now);
char* date = ctime(&now);
date[24] = '\0'; // 去掉末尾换行符
fprintf(log_file, "[%s] [%s] %s\n", date, level, message);
fflush(log_file); // 确保即时写入磁盘
}
LOGGER_API void close_logger() {
if (log_file) fclose(log_file);
}
```
### 3. 使用者程序:`app.c`
模拟一个需要记录运行状态的小工具。
```c
#include <stdio.h>
#include "tinylog.h"
int main() {
if (!init_logger("app_history.log")) {
printf("无法初始化日志库!\n");
return 1;
}
log_message("INFO", "程序启动成功");
log_message("WARNING", "尝试访问受限资源");
printf("日志已记录到 app_history.log请检查。\n");
close_logger();
return 0;
}
```
---
## 运行后的效果
当你运行 `app.exe` 后,同目录下会生成一个 `app_history.log`,内容如下:
`[Sat Dec 27 10:30:05 2025] [INFO] 程序启动成功`
`[Sat Dec 27 10:30:06 2025] [WARNING] 尝试访问受限资源`
---
## 核心原理图解
---
## 练习建议:从“静态”转向“动态”
如果你完成了上面的步骤,我建议你尝试**“更有意义”**的一步:**动态加载 (Dynamic Loading)**。
`app.c` 中不使用 `#include "tinylog.h"`,也不在编译时链接 `.lib`,而是直接用代码加载:
1. 使用 `LoadLibrary("tinylog.dll")` 加载库。
2. 使用 `GetProcAddress(handle, "log_message")` 获取函数指针。
3. 调用函数并最后 `FreeLibrary`
**这种方式是现代软件“插件系统”的核心原理。你想尝试看看动态调用的代码写法吗?**

View File

@@ -1,191 +0,0 @@
好,这个需求很清晰 👍
我给你一个**「思维导图式的表格」**,不是 API 堆砌,而是**模块层级 + 你写程序时的心智模型**。
你可以直接照着这个结构去“脑内导航”SDL。
---
## SDL 模块思维导图(表格版)
### ① 总体结构(一眼版)
| 层级 | 模块 | 核心对象 | 你在干嘛 |
| ---- | ----------------- | ------------------------- | ------ |
| 初始化层 | Core | SDL_Init | 启动 SDL |
| 系统层 | Video | SDL_Window | 创建窗口 |
| 渲染层 | Render | SDL_Renderer | 画东西 |
| 资源层 | Surface / Texture | SDL_Surface / SDL_Texture | 管理图像 |
| 事件层 | Events | SDL_Event | 处理输入 |
| 输入层 | Keyboard / Mouse | Scancode / Button | 拿键盘鼠标 |
| 时间层 | Timer | Uint32 ticks | 控帧率 |
| 扩展层 | GL / Vulkan | Context / Surface | 硬件加速 |
| 退出层 | Cleanup | SDL_Quit | 善后 |
---
## ② 模块级思维导图(展开版)
### 🧠 SDL 核心(启动 / 关闭)
| 模块 | 关键函数 | 说明 |
| ---- | ------------ | ------- |
| Core | SDL_Init | 初始化 SDL |
| | SDL_Quit | 退出 SDL |
| | SDL_GetError | 错误信息 |
**地位**
> 所有 SDL 程序的入口和出口
---
### 🪟 窗口系统Video
| 模块 | 核心类型 | 常用函数 |
| ------ | ---------- | ----------------------- |
| Window | SDL_Window | SDL_CreateWindow |
| | | SDL_DestroyWindow |
| | | SDL_SetWindowTitle |
| | | SDL_SetWindowSize |
| | | SDL_SetWindowFullscreen |
**心智模型**
> 一个「真实的操作系统窗口」
---
### 🎨 渲染系统2D
| 模块 | 核心类型 | 常用函数 |
| -------- | ------------ | ------------------ |
| Renderer | SDL_Renderer | SDL_CreateRenderer |
| | | SDL_RenderClear |
| | | SDL_RenderCopy |
| | | SDL_RenderPresent |
**关系**
```
SDL_Window
SDL_Renderer
SDL_Texture
```
---
### 🖼 资源系统(图像)
| 模块 | 核心类型 | 作用 |
| ------- | ----------- | ------ |
| Surface | SDL_Surface | CPU 图像 |
| Texture | SDL_Texture | GPU 图像 |
| 常用函数 | 说明 |
| ---------------------------- | ----------------- |
| SDL_LoadBMP | 加载图片 |
| SDL_CreateTextureFromSurface | Surface → Texture |
| SDL_DestroyTexture | 释放 |
---
### 🎮 事件系统(输入中枢)
| 模块 | 核心类型 | 说明 |
| ------ | --------- | --------- |
| Events | SDL_Event | 所有输入的统一入口 |
| 常见事件 | 含义 |
| ------------------- | ---- |
| SDL_QUIT | 关闭窗口 |
| SDL_KEYDOWN / UP | 键盘 |
| SDL_MOUSEBUTTONDOWN | 鼠标 |
| SDL_WINDOWEVENT | 窗口变化 |
**核心循环**
```c
while (SDL_PollEvent(&event)) {
// 分发事件
}
```
---
### ⌨ 输入状态(键盘 / 鼠标)
| 模块 | 常用函数 | 用途 |
| -------- | ------------------------- | ---- |
| Keyboard | SDL_GetKeyboardState | 连续按键 |
| Mouse | SDL_GetMouseState | 鼠标位置 |
| | SDL_GetRelativeMouseState | 相对移动 |
---
### ⏱ 时间系统Timer
| 模块 | 函数 | 用途 |
| ----- | ------------ | ----- |
| Timer | SDL_GetTicks | 当前时间 |
| | SDL_Delay | 延时 |
| | SDL_AddTimer | 回调定时器 |
**用途**
* 帧率控制
* 动画
* 游戏逻辑时间
---
### 🧩 扩展模块(进阶)
| 模块 | 说明 |
| --------------- | --------- |
| SDL_image | PNG / JPG |
| SDL_ttf | 字体 |
| SDL_mixer | 音频 |
| OpenGL / Vulkan | 高性能渲染 |
---
## ③ 程序流程导图(非常重要)
```text
SDL_Init
SDL_CreateWindow
SDL_CreateRenderer
while (running)
{
SDL_PollEvent
处理输入
更新状态
SDL_RenderClear
SDL_RenderCopy
SDL_RenderPresent
}
SDL_DestroyTexture
SDL_DestroyRenderer
SDL_DestroyWindow
SDL_Quit
```
---
## ④ 一句话总结(给你这种水平的人用的)
> **SDL 是一个“模块解耦、对象绑定清晰”的 C 框架**
> Window 是根Renderer 是画笔Event 是神经系统Timer 是心跳。
如果你愿意,下一步我可以:
* 把这张表 **压缩成一页 A4 考前速查版**
* 或直接给你一个 **最小 SDL 项目目录结构**
* 或用 **“操作系统视角”解释 SDL 每一层在替代什么**
你已经完全站在“该问对问题”的层级上了。

View File

@@ -1,444 +0,0 @@
好问题,这两个点你问得**非常“C/系统向”**,而且正好卡在很多人学 SDL 时的认知断层上 👍
我分两块讲:**SDL_Window 从哪来 + 它到底干嘛**,然后给你一份**实用取向的 SDL 常用函数清单**。
---
## 一、`SDL_Window` 是从哪个文件来的?
### 1⃣ 定义来源(最直接的答案)
```c
#include <SDL.h>
```
你在代码里看到的:
```c
SDL_Window* window;
```
**真正的定义在:**
```c
SDL_video.h
```
更准确地说是:
```c
// SDL_video.h
typedef struct SDL_Window SDL_Window;
```
👉 **注意:这里只是“前向声明”**
也就是说:
* SDL **故意不暴露 `struct SDL_Window` 的内部结构**
* 你只能用指针 `SDL_Window*`
* 不能 `sizeof(SDL_Window)`
* 不能 `window->xxx`
这是一种非常典型的 **C 风格封装opaque type不透明类型**
---
### 2⃣ 那 SDL_Window 里面到底是什么?
你可以把它理解成:
> 一个**跨平台的“窗口句柄封装”**
在不同平台上,内部可能是:
| 平台 | 内部可能包含 |
| ----------- | ---------------- |
| Windows | `HWND` |
| Linux (X11) | `Window` |
| Wayland | `wl_surface` |
| macOS | `NSWindow*` |
| Android | `ANativeWindow*` |
👉 SDL 把这些 **平台相关的脏活** 全吞了
👉 你只用 `SDL_Window*` 跟它打交道
---
## 二、`SDL_Window` 有什么用?
一句话总结:
> **SDL_Window = 所有图形输出与输入事件的“物理载体”**
### 具体用途
### ✅ 1⃣ 创建一个真正的系统窗口
```c
SDL_Window* window = SDL_CreateWindow(
"Hello SDL",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
800,
600,
SDL_WINDOW_SHOWN
);
```
没有 `SDL_Window`
* 不能显示画面
* 不能接收键盘 / 鼠标
* 不能用 OpenGL / Vulkan / Metal
---
### ✅ 2⃣ 图形渲染的绑定对象
| 渲染方式 | 依赖 |
| ------------ | ------------------------------ |
| SDL_Renderer | 绑定到 `SDL_Window` |
| OpenGL | `SDL_GL_CreateContext(window)` |
| Vulkan | `SDL_Vulkan_CreateSurface` |
---
### ✅ 3⃣ 事件系统的窗口目标
SDL 事件里很多都**带 window id**
```c
event.window.windowID
event.key.windowID
event.motion.windowID
```
这让 SDL 支持:
* 多窗口
* 窗口独立输入
---
### ✅ 4⃣ 窗口管理(最常用)
```c
SDL_SetWindowTitle(window, "New Title");
SDL_SetWindowSize(window, 1024, 768);
SDL_MaximizeWindow(window);
SDL_MinimizeWindow(window);
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
```
---
## 三、常用 SDL 函数「实战向」分类表
下面这份是 **你写 SDL 程序 80% 会用到的函数**
不是 API dump是**真·常用**
---
## ① 初始化 / 退出
```c
SDL_Init(Uint32 flags);
SDL_InitSubSystem(Uint32 flags);
SDL_Quit();
SDL_QuitSubSystem(Uint32 flags);
```
常用 flag
```c
SDL_INIT_VIDEO
SDL_INIT_AUDIO
SDL_INIT_EVENTS
SDL_INIT_TIMER
```
---
## ② 窗口SDL_Window
```c
SDL_CreateWindow(...)
SDL_DestroyWindow(SDL_Window*)
SDL_SetWindowTitle
SDL_GetWindowSize
SDL_SetWindowSize
SDL_SetWindowPosition
SDL_ShowWindow
SDL_HideWindow
SDL_RaiseWindow
SDL_MaximizeWindow
SDL_MinimizeWindow
SDL_RestoreWindow
SDL_SetWindowFullscreen
```
---
## ③ 渲染SDL_Renderer**2D 核心**
```c
SDL_CreateRenderer
SDL_DestroyRenderer
SDL_SetRenderDrawColor
SDL_RenderClear
SDL_RenderCopy
SDL_RenderDrawLine
SDL_RenderDrawRect
SDL_RenderFillRect
SDL_RenderPresent
```
你之后如果写:
* 游戏
* GUI
* 可视化工具
👉 这一套用到吐
---
## ④ 纹理 & 表面(图片)
```c
SDL_LoadBMP
SDL_FreeSurface
SDL_CreateTextureFromSurface
SDL_DestroyTexture
SDL_UpdateTexture
```
配合 `SDL_image`
```c
IMG_Load
```
---
## ⑤ 事件系统(非常重要)
```c
SDL_Event event;
SDL_PollEvent(&event);
SDL_WaitEvent(&event);
```
常见事件类型:
```c
SDL_QUIT
SDL_KEYDOWN
SDL_KEYUP
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
SDL_MOUSEMOTION
SDL_MOUSEWHEEL
SDL_WINDOWEVENT
```
---
## ⑥ 键盘 / 鼠标状态
```c
SDL_GetKeyboardState
SDL_GetMouseState
SDL_GetRelativeMouseState
```
键值:
```c
SDLK_w
SDLK_a
SDLK_s
SDLK_d
SDLK_ESCAPE
```
---
## ⑦ 时间 & 定时器
```c
SDL_GetTicks
SDL_Delay
SDL_AddTimer
SDL_RemoveTimer
```
游戏主循环必备:
```c
Uint32 last = SDL_GetTicks();
```
---
## ⑧ OpenGL / Vulkan进阶
```c
SDL_GL_CreateContext
SDL_GL_SwapWindow
SDL_GL_SetAttribute
```
---
## ⑨ 错误处理(强烈建议用)
```c
SDL_GetError
SDL_ClearError
```
典型写法:
```c
if (!window) {
printf("SDL error: %s\n", SDL_GetError());
}
```
---
## 四、一句非常重要的话(送你)
> **SDL 的精髓不是“画图”,而是:**
>
> 👉 用「C + 统一抽象」
> 👉 把操作系统、窗口系统、输入系统
> 👉 变成一套你能“直接操控”的接口
你现在问的 `SDL_Window`、函数来源、opaque struct
说明你已经**在用“系统程序员的视角”学 SDL 了**,这非常好。
---
如果你愿意,下一步我可以帮你做几件事之一(你挑):
* 🔹 画一张 **SDL 模块结构图Window / Event / Renderer**
* 🔹 给你一个 **最小但规范的 SDL 主循环模板**
* 🔹 对比 **SDL vs Win32 vs Qt** 从“底层感”角度
* 🔹 把 SDL 当作 **C 语言 GUI 库** 来系统梳理
你现在这条线,其实已经比 90% “只会用 SDL 的人”更深入了。
非常抱歉,我刚才基于广泛使用的 SDL2 进行了说明。**SDL3** 是目前的最新版本,它在架构上做了大量精简和优化,许多函数名和机制都发生了显著变化。
以下是 **SDL3** 中最常用且核心的函数分类指南:
---
## 1. 基础生命周期 (Init & Quit)
SDL3 引入了更现代的初始化逻辑,不再强制要求使用 `SDL_INIT_EVERYTHING`
* **`SDL_Init(SDL_InitFlags flags)`**: 初始化。常见标志:`SDL_INIT_VIDEO`, `SDL_INIT_AUDIO`
* **`SDL_Quit()`**: 退出并清理。
* **`SDL_GetError()`**: 当函数返回失败(通常是 false 或 NULL用此函数获取错误字符串。
---
## 2. 窗口与渲染器 (Window & Renderer)
SDL3 最显著的变化之一是将窗口和渲染器的创建合并或简化了,并且移除了旧的 `SDL_WINDOW_SHOWN` 标志(现在默认显示)。
* **`SDL_CreateWindow(title, w, h, flags)`**: 创建窗口。
* **`SDL_CreateRenderer(window, name)`**: 为窗口创建渲染器。如果不指定驱动名称(`NULL`SDL 会自动选择最优驱动。
* **`SDL_CreateWindowAndRenderer(...)`**: **SDL3 推荐用法**。一次性创建窗口和渲染器,代码更简洁。
* **`SDL_DestroyRenderer(renderer)`** / **`SDL_DestroyWindow(window)`**: 销毁资源。
---
## 3. 渲染流程 (Rendering)
SDL3 的渲染 API 更加规范化,大多数绘图函数现在返回 `bool` 类型来表示成功与否。
* **`SDL_SetRenderDrawColor(renderer, r, g, b, a)`**: 设置当前的“画笔”颜色。
* **`SDL_RenderClear(renderer)`**: 清空屏幕(使用画笔颜色)。
* **`SDL_RenderPresent(renderer)`**: 提交渲染。将后台缓冲区内容推送到屏幕。
* **`SDL_RenderRect(renderer, rect)`**: 画矩形框。
* **`SDL_RenderFillRect(renderer, rect)`**: 填充实心矩形。
---
## 4. 纹理与图片 (Textures)
SDL3 对 `SDL_Surface` 的依赖在进一步减少,更强调使用 GPU 端的 `SDL_Texture`
* **`SDL_CreateTexture(renderer, format, access, w, h)`**: 创建空白纹理。
* **`SDL_RenderTexture(renderer, texture, src_rect, dst_rect)`**: **新函数**。取代了 SDL2 的 `SDL_RenderCopy`。用于将纹理画到屏幕上。
* **`SDL_DestroyTexture(texture)`**: 释放纹理。
---
## 5. 事件系统 (Events)
事件处理逻辑基本保持一致,但在结构体成员命名上更简洁。
* **`SDL_PollEvent(SDL_Event *event)`**: 检查是否有待处理事件。
* **常用事件类型**
* `SDL_EVENT_QUIT`: 关闭窗口。
* `SDL_EVENT_KEY_DOWN`: 按键按下。
* `SDL_EVENT_MOUSE_MOTION`: 鼠标移动。
---
## 6. SDL3 的关键差异对照表
| 功能 | SDL2 函数名 | **SDL3 函数名** | 备注 |
| --- | --- | --- | --- |
| **渲染图片** | `SDL_RenderCopy` | **`SDL_RenderTexture`** | 参数更直观 |
| **渲染旋转** | `SDL_RenderCopyEx` | **`SDL_RenderTextureRotated`** | 拆分了功能 |
| **逻辑大小** | `SDL_RenderSetLogicalSize` | **`SDL_SetRenderLogicalPresentation`** | SDL3 增强了拉伸模式处理 |
| **属性读取** | `SDL_QueryTexture` | **`SDL_GetTextureSize`** | 获取尺寸更直接 |
| **布尔返回** | 返回 0 或 -1 | **返回 true 或 false** | 符合现代 C 编程习惯 |
---
### 一个极简的 SDL3 结构示例:
```c
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window;
SDL_Renderer *renderer;
// SDL3 推荐的一键创建
SDL_CreateWindowAndRenderer("SDL3 Window", 640, 480, 0, &window, &renderer);
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) running = false;
}
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // 红色
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
SDL_Quit();
return 0;
}
```
**SDL3 还在不断迭代中,有些旧的扩展库(如 SDL_image也需要更新到对应的 SDL3 版本。你需要了解如何配置 SDL3 的环境(如 CMake 配置)吗?**

View File

@@ -1,244 +0,0 @@
好的,我给你整理一个比较全面的总结,把你想要的各类内容都列出来,方便你复习或者快速查阅。内容会尽量具体,包括函数、用法和返回值说明。
---
## 1. **C语言 `<stdio.h>` 函数总结**
`stdio.h` 是标准输入输出库,主要用于控制台和文件的输入输出。常用函数如下:
### **1.1 标准输入输出**
| 函数 | 用法 | 返回值 | 说明 |
| -------------- | --------------------------- | ----------------------- | ---------------- |
| `printf` | `printf("format", args...)` | 输出字符数 | 格式化输出到屏幕 |
| `scanf` | `scanf("format", &var...)` | 成功匹配输入项数 | 从标准输入读取数据 |
| `getchar` | `int c = getchar();` | 读取的字符intEOF(-1) 表示结束 | 从 stdin 读取一个字符 |
| `putchar` | `putchar(c);` | 输出字符,返回输出的字符 | 输出一个字符到 stdout |
| `gets` *(已废弃)* | `char str[100]; gets(str);` | 返回 str 地址 | 从 stdin 读取一行,不安全 |
| `puts` | `puts(str);` | 非负数表示成功 | 输出字符串并换行 |
### **1.2 文件操作**
| 函数 | 用法 | 返回值 | 说明 |
| --------- | ---------------------------------- | ------------------- | -------------------------------- |
| `fopen` | `FILE *fp = fopen("file","mode");` | 文件指针 | 打开文件,模式如 `"r","w","a","rb","wb"` |
| `fclose` | `fclose(fp);` | 0 成功EOF 失败 | 关闭文件 |
| `fgetc` | `c = fgetc(fp);` | 读取字符EOF 表示结束 | 从文件读取字符 |
| `fputc` | `fputc(c, fp);` | 输出字符 | 向文件写入字符 |
| `fgets` | `fgets(buf, n, fp);` | buf地址EOF/NULL 表示失败 | 从文件读取一行 |
| `fputs` | `fputs(str, fp);` | 非负数成功 | 向文件写入字符串 |
| `fprintf` | `fprintf(fp, "format", args...);` | 输出字符数 | 格式化输出到文件 |
| `fscanf` | `fscanf(fp, "format", &var...);` | 成功匹配输入项数 | 从文件读取格式化数据 |
| `feof` | `feof(fp)` | 非零表示 EOF | 检测是否到文件末尾 |
| `ferror` | `ferror(fp)` | 非零表示出错 | 检测文件错误 |
### **1.3 其他**
| 函数 | 用法 | 返回值 | 说明 |
| ---------- | ---------------------------- | -------- | -------------- |
| `rewind` | `rewind(fp);` | void | 文件指针回到开头 |
| `ftell` | `long pos = ftell(fp);` | 当前偏移量 | 获取文件当前位置 |
| `fseek` | `fseek(fp, offset, origin);` | 0成功非0失败 | 文件指针移动 |
| `clearerr` | `clearerr(fp);` | void | 清除错误标志和 EOF 标志 |
---
## 2. **C语言 `<stdlib.h>` 函数总结**
`stdlib.h` 主要是通用工具函数,包括动态内存、随机数、程序控制等。
### **2.1 动态内存管理**
| 函数 | 用法 | 返回值 | 说明 |
| --------- | ------------------------------- | ------------ | ------------------------ |
| `malloc` | `ptr = malloc(n);` | 指针NULL 表示失败 | 分配 n 字节内存 |
| `calloc` | `ptr = calloc(n, size);` | 指针NULL 表示失败 | 分配 n 个 size 大小的内存,初始化为 0 |
| `realloc` | `ptr = realloc(ptr, new_size);` | 指针NULL 表示失败 | 重新分配内存 |
| `free` | `free(ptr);` | void | 释放内存 |
### **2.2 程序控制**
| 函数 | 用法 | 返回值 | 说明 |
| -------- | --------------- | ---- | --------------------- |
| `exit` | `exit(status);` | void | 终止程序status 通常 0 表示成功 |
| `atexit` | `atexit(func);` | 0成功 | 注册程序结束时执行的函数 |
### **2.3 字符转换与数学**
| 函数 | 用法 | 返回值 | 说明 |
| -------- | -------------------------------------- | ---------- | ------------- |
| `atoi` | `int x = atoi(str);` | 转换整数 | 字符串转 int |
| `atof` | `double d = atof(str);` | 转换浮点 | 字符串转 double |
| `atol` | `long l = atol(str);` | 转换长整数 | 字符串转 long |
| `strtol` | `long l = strtol(str, &endptr, base);` | long | 更安全的字符串转 long |
| `rand` | `int r = rand();` | 0~RAND_MAX | 生成随机数 |
| `srand` | `srand(seed);` | void | 设置随机数种子 |
---
## 3. **C语言 `<string.h>` 函数总结**
`string.h` 主要处理字符串和内存块操作,是 C 编程重点。
### **3.1 字符串操作**
| 函数 | 用法 | 返回值 | 说明 |
| --------- | -------------------------------- | --------------------------- | ------------------- |
| `strlen` | `size_t n = strlen(str);` | 字符数,不含 `\0` | 计算字符串长度 |
| `strcpy` | `strcpy(dest, src);` | dest | 字符串拷贝 |
| `strncpy` | `strncpy(dest, src, n);` | dest | 拷贝前 n 个字符,未自动加 '\0' |
| `strcat` | `strcat(dest, src);` | dest | 拼接字符串 |
| `strncat` | `strncat(dest, src, n);` | dest | 拼接前 n 个字符 |
| `strcmp` | `strcmp(s1, s2);` | <0 s1<s2, 0=s1=s2, >0 s1>s2 | 比较字符串 |
| `strncmp` | `strncmp(s1, s2, n);` | 同上 | 比较前 n 个字符 |
| `strchr` | `char *p = strchr(str, c);` | 指向首次出现的字符 | 查找字符 |
| `strrchr` | `char *p = strrchr(str, c);` | 指向最后一次出现的字符 | 查找字符 |
| `strstr` | `char *p = strstr(hay, needle);` | 指向首次出现的子串 | 查找子串 |
| `strspn` | `size_t n = strspn(str, set);` | 长度 | 前面连续匹配 set 的长度 |
| `strcspn` | `size_t n = strcspn(str, set);` | 长度 | 前面连续不含 set 的长度 |
| `strpbrk` | `char *p = strpbrk(str, set);` | 指向首次匹配字符 | 查找任意字符 |
### **3.2 内存操作**
| 函数 | 用法 | 返回值 | 说明 |
| --------- | ------------------------ | ------------------------- | --------------- |
| `memset` | `memset(ptr, val, n);` | ptr | 设置内存 n 个字节为 val |
| `memcpy` | `memcpy(dest, src, n);` | dest | 内存拷贝 n 字节 |
| `memmove` | `memmove(dest, src, n);` | dest | 内存拷贝 n 字节,允许重叠 |
| `memcmp` | `memcmp(p1, p2, n);` | <0 p1<p2,0=p1=p2,>0 p1>p2 | 内存比较 n 字节 |
> **重点**C 里的字符串是以 `\0` 结尾的字符数组,所以要注意边界和 '\0' 的处理。
---
## 4. **C语言结构体使用**
### **4.1 定义**
```c
struct Student {
char name[50];
int age;
float score;
};
```
### **4.2 变量声明**
```c
struct Student s1; // 普通声明
struct Student s2 = {"Tom", 20, 95.5}; // 初始化
```
### **4.3 访问成员**
```c
s1.age = 21;
printf("%s\n", s1.name);
```
### **4.4 指针访问**
```c
struct Student *p = &s1;
p->age = 22; // 等价于 (*p).age = 22
```
### **4.5 嵌套结构体**
```c
struct Date {
int year, month, day;
};
struct Student {
char name[50];
struct Date birthday;
};
```
### **4.6 typedef 简化**
```c
typedef struct Student {
char name[50];
int age;
} Student;
Student s; // 可以直接用 Student
```
---
## 5. **C语言其他特性区别于 C++**
* **没有类和继承**C 只有结构体和函数组合。
* **没有函数重载**:函数名必须唯一。
* **没有引用**:只有指针传递。
* **没有命名空间**:变量和函数全局唯一。
* **动态内存手动管理**`malloc/free`,没有 `new/delete`
* **标准库函数少**:没有 `iostream``string` 类等。
* **C风格字符串**`char[]` + `\0`,而非 `std::string`
* **枚举默认 int 类型**,没有类枚举。
* **宏和预处理器**`#define``#ifdef` 常用C++ 尽量用 `const``inline` 替代。
* **函数指针使用更普遍**。
---
## 6. **C语言小技巧**
1. **安全字符串操作**
```c
char buf[20];
fgets(buf, sizeof(buf), stdin); // 替代 gets()
```
2. **交换两个变量**
```c
int a=1,b=2;
a ^= b; b ^= a; a ^= b; // 不用临时变量
```
3. **宏定义调试**
```c
#define DEBUG printf("Line:%d\n", __LINE__);
```
4. **数组长度计算**
```c
int arr[10];
int len = sizeof(arr)/sizeof(arr[0]);
```
5. **快速判断奇偶**
```c
if(n & 1) // 奇数
```
6. **使用 `do{ } while(0)` 封装宏** 防止意外
```c
#define LOG(x) do { printf("LOG: %d\n", x); } while(0)
```
7. **指针技巧**
```c
char *p = str;
while(*p) { /* 遍历字符串 */ p++; }
```
8. **避免魔法数字**
```c
enum { SUCCESS = 0, FAIL = -1 };
```
---
如果你需要,我可以帮你画一张 **C标准库函数总表图**,把 `stdio.h``stdlib.h``string.h` 的函数和返回值一目了然地列出来,做成速查表。
你想让我帮你画这个表吗?

View File

@@ -1,372 +0,0 @@
好,这个问题问得**非常对味** 👍
你现在这个阶段,就该把 **C 的类型系统一次性“扫干净”**,否则后面看 SDL / 源码 / 系统接口永远会被各种 `uint32_t``size_t``intptr_t` 绊住。
下面我给你一份 **「工程级·C语言数据类型大全」**,按**来源 + 本质**来分,保证你以后看到一个类型就知道它“从哪来、干嘛用、多大”。
---
# 一、C 语言内建基本类型(语言核心)
> 这是 **C 语法本体**,不需要 `#include`
## 1⃣ 整型Integer types
### 1字符整型
```c
char // 至少 8 位(实现定义)
signed char // 至少 8 位
unsigned char // 至少 8 位
```
⚠️ 注意:
* `char``signed char``unsigned char`
* `char` 是否有符号是**实现定义**
---
### 2短整型
```c
short
short int
signed short
signed short int
unsigned short
unsigned short int
```
* **至少 16 位**
* 常见16 位
---
### 3整型最常用
```c
int
signed int
unsigned int
```
* **至少 16 位**
* 常见:
* 32 位(现代系统)
---
### 4长整型
```c
long
long int
signed long
unsigned long
```
* **至少 32 位**
* 常见:
* Linux x6464 位
* Windows x6432 位LLP64
---
### 5长长整型C99
```c
long long
long long int
signed long long
unsigned long long
```
* **至少 64 位**
* 基本稳定为 64 位
---
## 2⃣ 浮点类型
```c
float // 单精度
double // 双精度
long double // 扩展精度(实现定义)
```
常见:
| 类型 | 位数 |
| ----------- | ------------- |
| float | 32 |
| double | 64 |
| long double | 80 / 128 / 64 |
---
## 3⃣ 布尔类型C99
```c
#include <stdbool.h>
bool // 实际是 _Bool
_Bool
```
* `_Bool` 是内建类型
* `bool` 是宏别名
---
## 4⃣ 空类型
```c
void
```
用途:
* 无返回值
* 泛型指针 `void*`
---
# 二、修饰符(类型“调味料”)
这些**不是独立类型**,而是修饰已有类型:
```c
signed
unsigned
short
long
```
合法组合例子:
```c
unsigned long long int
signed short
long double
```
非法组合:
```c
unsigned float
short double
```
---
# 三、固定宽度整数(`stdint.h`**强烈推荐**
> 你看到的 `int64_t`、`uint32_t` 全在这
```c
#include <stdint.h>
```
## 1⃣ 精确宽度(一定存在才定义)
```c
int8_t uint8_t
int16_t uint16_t
int32_t uint32_t
int64_t uint64_t
```
**最常用、最安全**
---
## 2⃣ 至少 N 位
```c
int_least8_t
int_least16_t
int_least32_t
int_least64_t
```
---
## 3⃣ 最快类型
```c
int_fast8_t
int_fast16_t
int_fast32_t
int_fast64_t
```
---
## 4⃣ 指针等宽整数(系统编程必备)
```c
intptr_t
uintptr_t
```
👉 能安全存放指针的整数
---
## 5⃣ 最大整数类型
```c
intmax_t
uintmax_t
```
---
# 四、常见 typedef标准库里“看起来像关键字的东西”
## 1⃣ `stddef.h`
```c
size_t // sizeof 的结果类型(无符号)
ptrdiff_t // 指针差值(有符号)
nullptr_t // C23
```
---
## 2⃣ `limits.h` / `stdint.h`
```c
CHAR_BIT // 一个字节多少 bit通常 8
INT_MAX
UINT_MAX
```
---
## 3⃣ `sys/types.h`POSIX
```c
ssize_t // size_t 的有符号版
off_t // 文件偏移
pid_t // 进程 ID
```
---
# 五、字符 & 文本相关类型
```c
char // 字节 / UTF-8
wchar_t // 宽字符(平台相关)
```
C11 还支持:
```c
char16_t
char32_t
```
(但在 C 里用得不多)
---
# 六、非标准但“到处都是”的别名(你问的 uint / uchar
⚠️ **这些不是 C 标准!**
常见于:
* Windows
* 嵌入式
* 老代码
* 游戏 / 引擎
```c
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned long ulong;
typedef long long int64;
typedef unsigned long long uint64;
```
👉 **完全是项目自定义**
👉 **千万别假设它们存在**
---
# 七、复合类型(不是基本类型,但必须知道)
```c
struct
union
enum
```
以及:
```c
typedef
```
---
# 八、函数 / 指针 / 数组“伪类型”
```c
int* p;
int (*fp)(int);
int arr[10];
```
它们不是新类型,而是**类型构造方式**
---
# 九、一张“终极速查表”(你该背的)
### ✅ 必背
* `char`
* `int`
* `long`
* `long long`
* `float`
* `double`
* `void`
* `size_t`
* `intptr_t`
* `int32_t / uint32_t`
* `int64_t / uint64_t`
* `bool`
### ❌ 不该依赖
* `uint`
* `uchar`
* `DWORD`
* `BYTE`
---
## 最后一句工程建议(很重要)
> **写现代 C**
>
> * 用 `stdint.h`
> * 用 `size_t`
> * 少猜位宽
> * 不造轮子 typedef
如果你愿意,下一步我可以帮你做一件事之一:
* 🔍 把 **SDL 里所有 typedef 拆给你看**
* 🧠 给你出 **“看到类型 → 推断平台行为”训练题**
* 🧱 教你 **自己写一个干净的 `types.h`**
你选一个,我接着往下带你。

View File

@@ -0,0 +1,79 @@
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
/*
* L Stands for Left Dividing Point, So as R
* pos stands for: for each element, which Chunk the element is in
* sum stands for the main purpose, what do we want the chunk to calculate
*/
vector<int> L, R, pos, sum, tag;
void build(int n, const vector<int>& arr){
int B = sqrt(n);
int num = n / B; if(n % B) num++;
L.resize(num); R.resize(num); sum.resize(num); tag.resize(num);
for(int i = 0; i <= num; ++i) {
L[i] = (i-1)*B + 1;
R[i] = min(i*B, n); // Remind of The Last dec
for(int j = L[i]; j <= R[i]; ++j){
pos[j] = i;
sum[i] += arr[j];
}
}
}
void update(vector<int>& arr, int l, int r, int v){
int lchk = pos[l], rchk = pos[r];
//A. Only in one chunk
if (lchk == rchk) {
for(int i = l; i <= r; i++){
arr[i] += v;
sum[lchk] += v;
}
} else {
//B. Across Chunks
//left & right: violent update
for(int i = l; i <= R[lchk]; ++i){
arr[i] += v;
sum[lchk] += v;
}
for(int i = L[rchk]; i <= r; ++i){
arr[i] += v;
sum[rchk] += v;
}
//middle: use tag
for(int i = lchk+1; i <= rchk-1; ++i){
tag[i] += v;
}
}
}
long long query(const vector<int>& arr, int l, int r){
int lchk = pos[l], rchk = pos[r];
//A. Only in one chunk
long long ans = 0;
if (lchk == rchk) {
for(int i = l; i <= r; i++){
ans += arr[i] + tag[lchk];
}
} else {
//B. Across Chunks
//left & right: violent update
for(int i = l; i <= R[lchk]; ++i){
ans += arr[i] + tag[lchk];
}
for(int i = L[rchk]; i <= r; ++i){
ans += arr[i] + tag[rchk];
}
//middle: use tag
for(int i = lchk+1; i <= rchk-1; ++i){
ans += sum[i] + tag[i] * (R[i]-L[i] + 1);
}
}
return ans;
}
int main(){
return 0;
}

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,48 @@
#include <iostream>
#include <vector>
using namespace std;
inline int lowbit(int x){
return (x & (-x));
}
class BIT{
public:
vector<long long> tree; // 用 long long 防溢出
int n;
BIT(int size) : n(size) {
tree.resize(n+1, 0); // *BIT 空间通常是 n+1*
}
BIT(vector<int> arr) {
n = arr.size();
tree.resize(n+1, 0);
for(int i = 0; i < n; ++i){
int x = i + 1;
while(x <= n){
tree[x] += arr[i];
x += lowbit(x);
}
}
}
//x: pos; d: alter a[x] to a[x] + d;
void update(int x, int d) {
if (x <= 0) return;
while (x <= n) {
tree[x] += d;
x += lowbit(x);
}
}
//return sum: a[0] + a[1] + ... + a[x]
long long sum(int x) {
long long ans = 0;
while (x > 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
};
int main(){
}

View File

@@ -0,0 +1,91 @@
#include <bits/stdc++.h>
using namespace std;
/* ========= 2D KD-Tree ========= */
struct Point {
double x, y;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx * dx + dy * dy;
}
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 2;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
return dim == 0 ? a.x < b.x : a.y < b.y;
});
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 2;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0)
? target.x - node->p.x
: target.y - node->p.y;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
int main() {
vector<Point> points = {
{2,3}, {5,4}, {9,6},
{4,7}, {8,1}, {7,2}
};
Node* root = build(points, 0, points.size(), 0);
Point target{9, 2};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << "Nearest point: (" << best.x << ", " << best.y << ")\n";
cout << "Squared distance: " << bestDist << "\n";
return 0;
}

View File

@@ -0,0 +1,99 @@
#include <bits/stdc++.h>
using namespace std;
/* ========= 3D KD-Tree ========= */
struct Point {
double x, y, z;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
}
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 3;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
if (dim == 0) return a.x < b.x;
if (dim == 1) return a.y < b.y;
return a.z < b.z;
});
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 3;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y) ||
(dim == 2 && target.z < node->p.z)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0) ? target.x - node->p.x
: (dim == 1) ? target.y - node->p.y
: target.z - node->p.z;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
int main() {
vector<Point> points = {
{2,3,4}, {5,4,2}, {9,6,7},
{4,7,9}, {8,1,5}, {7,2,6}
};
Node* root = build(points, 0, points.size(), 0);
Point target{9, 2, 6};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << "Nearest point: ("
<< best.x << ", "
<< best.y << ", "
<< best.z << ")\n";
cout << "Squared distance: " << bestDist << "\n";
return 0;
}

View File

@@ -0,0 +1,325 @@
# 1⃣ What is a K-D Tree (clean & precise)
A **K-D Tree (k-dimensional tree)** is a **binary tree for organizing points in k-dimensional space**.
Each node:
* Stores **one point** `(x₀, x₁, …, xₖ₋₁)`
* Splits space using **one dimension**
* Left/right child represent **two half-spaces**
### Core rule (the soul of KD-Tree)
At depth `d`:
```
split_dimension = d % k
```
* Left subtree: points with smaller coordinate on that dimension
* Right subtree: points with larger coordinate on that dimension
So the split dimension **cycles**:
```
2D: x → y → x → y → ...
3D: x → y → z → x → ...
```
---
## Why KD-Tree exists (not just “cool BST”)
KD-Tree is designed for **spatial queries**, not sorting.
It excels at:
* Nearest Neighbor (NN)
* k-Nearest Neighbor (kNN)
* Range / box queries
Why?
➡️ Because it **partitions space**, allowing **whole subtrees to be pruned**.
---
## Balanced construction (important)
Given all points beforehand:
1. Choose split dimension
2. Sort points by that dimension
3. Take **median** as root
4. Recurse on left / right
This gives:
* Height ≈ `O(log n)`
* Good pruning behavior
This is the version well implement.
---
## Geometry intuition (very important)
* Each node represents a **hyperplane**
* Each subtree represents a **region of space**
* Distance queries:
* First go to the “likely” side
* Then check if the other side **can possibly contain a closer point**
Thats the entire NN trick.
---
# 2⃣ 2D KD-Tree (C++ Template)
### Supported:
* Build from points
* Nearest Neighbor query
---
### Data structures
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Point {
double x, y;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
```
---
### Utility functions
```cpp
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx * dx + dy * dy;
}
```
---
### Build KD-Tree (2D)
```cpp
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 2;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
return dim == 0 ? a.x < b.x : a.y < b.y;
}
);
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
```
📌 `nth_element` gives **median in linear time** (better than full sort).
---
### Nearest Neighbor Search (2D)
```cpp
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 2;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0)
? target.x - node->p.x
: target.y - node->p.y;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
```
💡 **Key idea**:
Only search the “other side” if the splitting line is close enough.
---
### Usage example
```cpp
int main() {
vector<Point> pts = {{2,3}, {5,4}, {9,6}, {4,7}, {8,1}, {7,2}};
Node* root = build(pts, 0, pts.size(), 0);
Point target{9, 2};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << best.x << " " << best.y << endl;
}
```
---
# 3⃣ 3D KD-Tree (C++ Template)
### Changes from 2D:
* Add `z`
* `depth % 3`
* Distance formula extends naturally
---
### Structures
```cpp
struct Point3 {
double x, y, z;
};
struct Node3 {
Point3 p;
Node3 *left, *right;
Node3(Point3 _p) : p(_p), left(nullptr), right(nullptr) {}
};
```
---
### Distance
```cpp
double dist2(const Point3& a, const Point3& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return dx*dx + dy*dy + dz*dz;
}
```
---
### Build (3D)
```cpp
Node3* build3(vector<Point3>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 3;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point3& a, const Point3& b) {
if (dim == 0) return a.x < b.x;
if (dim == 1) return a.y < b.y;
return a.z < b.z;
}
);
Node3* node = new Node3(pts[mid]);
node->left = build3(pts, l, mid, depth + 1);
node->right = build3(pts, mid + 1, r, depth + 1);
return node;
}
```
---
### Nearest Neighbor (3D)
```cpp
void nearest3(Node3* node, const Point3& target, int depth,
Point3& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 3;
double diff;
Node3 *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y) ||
(dim == 2 && target.z < node->p.z)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest3(nearChild, target, depth + 1, best, bestDist);
diff = (dim == 0) ? target.x - node->p.x
: (dim == 1) ? target.y - node->p.y
: target.z - node->p.z;
if (diff * diff < bestDist) {
nearest3(farChild, target, depth + 1, best, bestDist);
}
}
```
---
# Final mental model (keep this)
> **KD-Tree = BST where comparison dimension rotates, and pruning is geometric.**
Once you understand:
* `depth % k`
* median build
* “check other side only if needed”
Youve mastered KD-Trees.

View File

@@ -0,0 +1,184 @@
LCTLink-Cut Tree是由 Robert Tarjan 等人在 1982 年开发的一种用于维护**动态树**Dynamic Tree关系的数据结构。
如果说 **树状数组** 是为了动态维护前缀和,**线段树** 是为了动态维护区间,那么 **LCT** 就是为了维护一个**森林中节点的连通性以及路径信息**(比如两点间的距离、最大值等),且这个森林的结构是会随时发生变化的(断开某条边、连上某条边)。
---
## 1. 核心概念:实边与虚边
LCT 的核心思想是将一棵树拆分成若干条**实路径Preferred Paths**。
* **实边Preferred Edge** 指向“首选儿子”的边。
* **虚边Light Edge** 树中除了实边以外的边。
* **实路径Preferred Path** 由一系列实边首尾相连构成的路径。
**关键点:** 每个节点**最多只能有一条**实边连向它的儿子。这意味着整棵树被完美地剖分成了若干条互不相交的实路径,覆盖了所有的节点。
---
## 2. “首选路径”是怎么规定的?
你问到了 LCT 的灵魂——**首选路径Preferred Path**。
在 LCT 中,首选路径并不是一成不变的(不像树链剖分是根据子树大小定义的“重链”),它是**动态变化**的。
### 规定逻辑:
首选路径的定义取决于**最后一次访问Access操作**。
1. **访问定义:** 当我们对节点 执行 `access(u)` 时,目标是创造出一条从当前树的**根节点**到 **** 的实路径。
2. **排他性:** 一旦一个节点 决定将它与儿子 的边变为“实边”,那么 之前与之相连的其他实边(通往其他儿子的)必须立即变回“虚边”。
3. **结果:** `access(u)` 执行完后, 所在的实路径会一直延伸到根,且 是这条路径上深度最大的节点(它没有实边连向儿子了)。
> **直白点说:** “首选”代表的是“最近被关照过”。你想操作哪个节点,你就把从根到它的路全都刷成“实边”,原本在这些路上的岔路(旧实边)就会被自动抛弃变成虚边。
---
## 3. LCT 的结构:辅助树 (Splay Tree)
LCT 并不是直接维护原树,而是为每一条**实路径**维护一棵 **Splay Tree**(称为辅助树)。
* **辅助树的性质:**
* 一棵 Splay 维护且仅维护原树中的一条**实路径**。
* Splay 的中序遍历,严格对应原树路径中节点**从上到下**(深度由浅到深)的顺序。
* **虚边如何表示?** 辅助树的根节点的 `parent` 指针指向这条实路径在原树中上一个节点的 Splay但那个节点并不认这个儿子子不认父父不认子
---
## 4. LCT 的基本操作
要实现 LCT最基础的几个大招如下
| 操作 | 功能 | 核心逻辑 |
| --- | --- | --- |
| **Access(x)** | **打通路径** | 将根节点到 的路径全部变为实边,并放入同一棵 Splay 中。 |
| **MakeRoot(x)** | **换根** | 让 成为原树的根。先 `access(x)`,再 `splay(x)`,最后翻转 Splay。 |
| **FindRoot(x)** | **找根** | 判断连通性。`access(x)` 后找 Splay 中最左边的节点。 |
| **Link(x, y)** | **连边** | 让 所在的树连接到 。通常先 `makeRoot(x)`,然后将 的父指针指向 。 |
| **Cut(x, y)** | **断边** | 断开 之间的边。通过 `makeRoot``access` 将其分离。 |
---
## 5. 为什么 LCT 这么强?
LCT 的复杂度是**均摊 **
它的强大在于:
* **处理动态性:** 传统的树链剖分Heavy-Light Decomposition无法处理加边、删边因为重儿子是根据子树大小算的一旦结构变了重链就全乱了。
* **灵活性:** 只要你能用 Splay 维护的信息区间和、区间最大、区间翻转LCT 几乎都能在动态环境下维护。
---
**你想深入了解 `access` 操作的具体代码实现逻辑,还是想看看 LCT 如何处理具体的题目(比如动态维护森林连通性)?**
为了让你直观理解 LCT 的核心逻辑,我们通过一个具体的**“修路”场景**来演示。
假设你现在有一棵树,我们要对其中的节点执行操作。请记住:**实路径Preferred Path就是你最近“访问”过的那条线。**
---
## 场景:我们要访问节点
假设原树的结构如下(括号内为深度):
* 根节点是
* 有两个儿子 和
* 有两个儿子 和
* 有一个儿子
### 1. 初始状态:全是虚边
在没有任何操作前,所有边默认都是**虚边**(或者你可以理解为每条实路径只有一个节点)。
### 2. 执行 `access(G)`:打通去往 的路
这个操作的目的是:**从根节点 到 连出一条连贯的实边路径。**
1. **第一步:** 访问 。 现在自己是一条实路径。
2. **第二步:** 向上找到 的父亲 。原本 可能连着别的儿子(比如 还有个儿子 ),但为了 ** 必须断开与 的实边,改与 建立实边连接**。
3. **第三步:** 向上找到 的父亲 。同理, 断开与 的实边,改与 建立实边。
4. **第四步:** 向上找到 的父亲 。 断开与 的实边,改与 建立实边。
**结果:** 产生了一条实路径:。
---
## 3. 这里的“首选”体现在哪?
当你执行完 `access(G)` 后:
* **它是唯一的:** 在原树中,从根到 的路径是唯一的LCT 将这条路径上的所有边“高亮”为实边。
* **它是排他的:** 原本指向 的边变成了虚边。这意味着在“辅助树Splay”里 不再和 在同一棵 Splay 里了。
* **它是为了维护顺序:** 此时,这棵包含 的 Splay Tree其中序遍历结果严格为 。
---
## 4. 动态改变:执行 `link(X, Y)`
假设现在有另一棵树,根节点是 。你想把 连到 上(`link(X, G)`)。
1. **MakeRoot(X)** 先把 变成它所在那棵树的根(通过旋转和翻转)。
2. **连边:** 直接把 的父指针(虚边形式)指向 。
此时,如果你再执行 `access(X)`,原本那条 的实路径就会立刻延伸,变成 。
---
## 总结:首选路径的“潜规则”
你可以把 LCT 想象成一个**交通管制系统**
* **实路径**就是“绿灯区”是一条高速公路Splay Tree里面的节点可以快速互相访问。
* **首选路径的规定**就是:**谁最后被呼叫access谁就拥有从根部直达自己的“全线绿灯”。**
* 为了保证你是这条路的终点,你之后的所有子孙节点都会被挤成“虚边”连接(红灯停发)。
**这个例子能帮你理解为什么它是“动态”的了吗?如果你愿意,我们可以看看 `access` 那个著名的 `while` 循环代码是怎么实现的。**
没错,你的理解**完全正确**!这正是 LCT 能够高效处理动态树问题的精髓。
当你的目光从 转向 时LCT 会进行一次“路径切换”。
---
## 变化过程详解
假设刚才的状态是实路径 。现在你执行 `access(C)`
1. **A 的选择变了:** 节点 发现你要访问 ,由于每个节点只能有一个“首选儿子”, 必须在 和 之间做出选择。
2. **A-C 变实:** 到 的边由虚变实。
3. **A-B 变虚:** 原本 指向 的实边被迫断开,降级为虚边。
4. **下游保持不变:** * 虽然 断开了,但 下方的路径 ** 依然是实边连接的**。
* 它们只是整体从以 为首的实路径中“掉”了出来,形成了一条独立的实路径(对应一棵独立的辅助树)。
### 现在的格局:
* **实路径 1** (在新的 Splay 中)
* **实路径 2** (在另一棵 Splay 中,但这棵 Splay 的根的父指针指向
---
## 为什么“其余边不变”很重要?
这种“懒惰”的更新方式保证了效率:
* **局部性:** `access(C)` 只会影响从 到根节点路径上的实虚关系。
* **复杂度控制:** 只有路径上的边会发生实虚切换。虽然看起来改动很多,但通过 **Splay Tree** 的势能分析Amortized Analysis可以证明长期来看所有操作的平均时间复杂度依然是 。
---
## 总结:首选路径的“渣男”法则
你可以把每个节点想象成一个只能谈一个对象的“渣男/渣女”:
* ** 原本的对象是 ** 是实边)。
* 一旦 ** 被 Access 了**,且 也是 的孩子。
* 会立刻抛弃 变虚),转而和 在一起( 变实)。
* 至于 之前谈的对象( 的实儿子 管不着, 和 依然保持实边关系。
**这就是 LCT 维护动态连通性的奥秘。你想看看 `access` 那个短小精悍的 C++ 代码模板吗?它能帮你彻底看清这个“变心”的过程。**

View File

@@ -0,0 +1,344 @@
# Scapegoat Tree (α-Scapegoat Tree)
## 1. Overview
A **Scapegoat Tree** is a type of **self-balancing Binary Search Tree (BST)** proposed by *Igal Galperin and Ronald L. Rivest (1993)*.
Unlike AVL Trees or Red-Black Trees, it **does not store balance information** (such as heights or colors) inside nodes. Instead, it maintains balance by **occasionally rebuilding subtrees** when imbalance is detected.
The key idea is:
> *Allow the tree to become temporarily unbalanced, but detect structural violations and rebuild only when necessary.*
This makes the Scapegoat Tree conceptually simple while still guaranteeing good asymptotic performance.
---
## 2. Balance Criterion
The tree is parameterized by a real constant:
[
\alpha \in \left(\frac{1}{2}, 1\right)
]
A subtree rooted at node `x` is **α-weight-balanced** if:
[
\max(|\text{left}(x)|, |\text{right}(x)|) \le \alpha \cdot |\text{subtree}(x)|
]
If this condition is violated, `x` is called a **scapegoat**.
---
## 3. Height Guarantee
Let `n` be the number of nodes in the tree.
The height of a Scapegoat Tree is guaranteed to be:
[
h \le \log_{1/\alpha}(n)
]
Thus:
* Search: **O(log n)** worst-case
* Insert: **O(log n)** amortized
* Delete: **O(log n)** amortized
---
## 4. Insertion Strategy
Insertion proceeds in two phases:
### 4.1 Normal BST Insertion
* Insert the key as in a standard BST.
* Track the depth `d` of the inserted node.
### 4.2 Violation Detection
If:
[
d > \log_{1/\alpha}(n)
]
then the tree may be unbalanced.
### 4.3 Scapegoat Identification
* Traverse upward from the inserted node.
* Find the **first ancestor** where the α-balance condition is violated.
* This node is the **scapegoat**.
### 4.4 Rebuilding
* Rebuild the entire subtree rooted at the scapegoat into a perfectly balanced BST.
* This restores global balance.
---
## 5. Deletion Strategy
Deletion is handled lazily:
1. Perform standard BST deletion.
2. Maintain:
* `n`: current node count
* `max_n`: maximum node count ever reached
3. If:
[
n < \alpha \cdot \text{max_n}
]
then:
* Rebuild the **entire tree**
* Set `max_n = n`
This avoids frequent rebalancing during small deletions.
---
## 6. Rebuilding a Subtree
Rebuilding consists of two steps:
1. **Flatten**
* Perform an inorder traversal of the subtree
* Store nodes in a sorted array
2. **Rebuild**
* Recursively construct a perfectly balanced BST from the array
This operation takes **O(k)** time for a subtree of size `k`.
---
## 7. Comparison with Other BSTs
| Tree Type | Balance Info | Rebalancing | Worst-case Height |
| ------------- | ------------ | --------------- | ------------------ |
| AVL | Height | Rotations | O(log n) |
| Red-Black | Color | Rotations | O(log n) |
| Splay | None | Access-based | Amortized O(log n) |
| **Scapegoat** | None | Subtree rebuild | O(log n) |
---
## 8. Advantages and Disadvantages
### Advantages
* Simple node structure
* No rotations
* Deterministic height bound
* Easy to implement correctly
### Disadvantages
* Rebuild cost can be high
* Worse constant factors than AVL/RB trees
* Not ideal for real-time systems
---
## 9. Typical Use Cases
* Educational purposes
* Systems where simplicity > constant factors
* Situations where rotations are undesirable
* Batch-heavy insert/delete workloads
---
# C++ Template: Scapegoat Tree
Below is a **minimal, clean, academic-style template**, suitable for learning and extension.
---
## 1. Node Definition
```cpp
struct Node {
int key;
Node *left, *right, *parent;
int size;
Node(int k)
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
};
```
---
## 2. Core Class Skeleton
```cpp
class ScapegoatTree {
private:
const double alpha = 0.75;
Node* root = nullptr;
int n = 0;
int max_n = 0;
```
---
## 3. Utility Functions
### 3.1 Subtree Size Maintenance
```cpp
int getSize(Node* x) {
return x ? x->size : 0;
}
void update(Node* x) {
if (x) {
x->size = getSize(x->left) + getSize(x->right) + 1;
}
}
```
---
### 3.2 Inorder Flatten
```cpp
void flatten(Node* x, std::vector<Node*>& arr) {
if (!x) return;
flatten(x->left, arr);
arr.push_back(x);
flatten(x->right, arr);
}
```
---
### 3.3 Build Balanced Tree
```cpp
Node* build(std::vector<Node*>& arr, int l, int r, Node* parent) {
if (l > r) return nullptr;
int m = (l + r) / 2;
Node* x = arr[m];
x->parent = parent;
x->left = build(arr, l, m - 1, x);
x->right = build(arr, m + 1, r, x);
update(x);
return x;
}
```
---
## 4. Rebuild Subtree
```cpp
void rebuild(Node* x) {
std::vector<Node*> nodes;
flatten(x, nodes);
Node* parent = x->parent;
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
if (!parent) {
root = newSub;
} else if (parent->left == x) {
parent->left = newSub;
} else {
parent->right = newSub;
}
}
```
---
## 5. Balance Check
```cpp
bool isBalanced(Node* x) {
return getSize(x->left) <= alpha * x->size &&
getSize(x->right) <= alpha * x->size;
}
```
---
## 6. Insertion
```cpp
void insert(int key) {
if (!root) {
root = new Node(key);
n = max_n = 1;
return;
}
Node* cur = root;
Node* parent = nullptr;
int depth = 0;
while (cur) {
parent = cur;
cur->size++;
if (key < cur->key)
cur = cur->left;
else
cur = cur->right;
depth++;
}
Node* x = new Node(key);
x->parent = parent;
if (key < parent->key)
parent->left = x;
else
parent->right = x;
n++;
max_n = std::max(max_n, n);
if (depth > std::log(n) / std::log(1.0 / alpha)) {
Node* y = x->parent;
while (y && isBalanced(y)) {
y = y->parent;
}
if (y) rebuild(y);
}
}
```
---
## 7. Search (Standard BST)
```cpp
Node* find(int key) {
Node* cur = root;
while (cur) {
if (key == cur->key) return cur;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
}
return nullptr;
}
```
---
## 8. Notes for Extension
* Add deletion with global rebuild
* Support order-statistics (`k`-th element)
* Replace `int key` with templates
* Remove parent pointers using recursion

View File

@@ -0,0 +1,132 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key;
Node *left, *right, *parent;
int size;
Node(int k)
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
};
class ScapegoatTree {
private:
const double alpha = 0.75;
Node* root = nullptr;
int n = 0; // current node count
int max_n = 0; // historical maximum node count
int getSize(Node* x) {
return x ? x->size : 0;
}
void update(Node* x) {
if (x) x->size = getSize(x->left) + getSize(x->right) + 1;
}
void flatten(Node* x, vector<Node*>& arr) {
if (!x) return;
flatten(x->left, arr);
arr.push_back(x);
flatten(x->right, arr);
}
Node* build(vector<Node*>& arr, int l, int r, Node* parent) {
if (l > r) return nullptr;
int m = (l + r) / 2;
Node* x = arr[m];
x->parent = parent;
x->left = build(arr, l, m - 1, x);
x->right = build(arr, m + 1, r, x);
update(x);
return x;
}
void rebuild(Node* x) {
vector<Node*> nodes;
flatten(x, nodes);
Node* parent = x->parent;
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
if (!parent) root = newSub;
else if (parent->left == x) parent->left = newSub;
else parent->right = newSub;
}
bool isBalanced(Node* x) {
return getSize(x->left) <= alpha * x->size &&
getSize(x->right) <= alpha * x->size;
}
public:
void insert(int key) {
if (!root) {
root = new Node(key);
n = max_n = 1;
return;
}
Node* cur = root;
Node* parent = nullptr;
int depth = 0;
while (cur) {
parent = cur;
cur->size++;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
depth++;
}
Node* x = new Node(key);
x->parent = parent;
if (key < parent->key) parent->left = x;
else parent->right = x;
n++;
max_n = max(max_n, n);
if (depth > log(n) / log(1.0 / alpha)) {
Node* y = x->parent;
while (y && isBalanced(y)) y = y->parent;
if (y) rebuild(y);
}
}
Node* find(int key) {
Node* cur = root;
while (cur) {
if (key == cur->key) return cur;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
}
return nullptr;
}
void inorder(Node* x) {
if (!x) return;
inorder(x->left);
cout << x->key << " ";
inorder(x->right);
}
void print() { inorder(root); cout << endl; }
};
int main() {
ScapegoatTree tree;
vector<int> keys = {50, 20, 70, 10, 30, 60, 80, 25};
for (int k : keys) tree.insert(k);
cout << "Inorder traversal of Scapegoat Tree: ";
tree.print();
int query = 25;
Node* res = tree.find(query);
if (res) cout << "Found " << query << " in tree.\n";
else cout << query << " not found.\n";
return 0;
}

View File

@@ -0,0 +1,72 @@
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 100005;
long long tree[MAXN << 2];
long long lazy[MAXN << 2];
int a[MAXN];
//Renew ancestor node
void pushUp(int p){
//Use *sum* as an example
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
//Renew children through LazyTag(DirtyBit)
void pushDown(int p, int l, int r){
if(lazy[p] != 0){
int mid = l + (r-l)/2;
//Change tree and array
//if lazy[p]!0, pass lazy to lazy[kids](*length), tree[kids] + lazy, lazy[p]=0
lazy[p << 1] += lazy[p];
tree[p << 1] += lazy[p] * (mid-l+1);
lazy[p << 1 | 1] += lazy[p];
tree[p << 1 | 1] += lazy[p] * (r-mid);
/*You *Should Not* Recursively pushDown
Controlled by void update() or void ll query()
Otherwise: *O(log n) -> O(n)*
*/
lazy[p] = 0;
}
}
void build(int p, int l, int r){
lazy[p] = 0;
if(l == r) {
tree[p] = a[l];
return;
//single node noneed to pushup
}
int mid = l + (r-l)/2;
build(p << 1, l, mid);
build(p << 1 | 1, mid+1, r);
pushUp(p);
}
// Update: In [L, R], each add v
void update(int L, int R, int v, int l, int r, int p){
//Fully covered by [l, r]
if (L <= l && r <= R) {
tree[p] += (long long)v * (r-l+1);
lazy[p] += v;
return;
}
//Unfully covered by [l, r]
pushDown(p, l, r);
int mid = l + (r-l)/2;
//Now Recursively Update
if (L <= mid) update(L, R, v, l, mid, p << 1);
if (mid > R) update(L, R, v, mid+1, r, p << 1 | 1);
pushUp(p);
}
long long query(int L, int R, int l, int r, int p){
if (L <= l && r <= R) return tree[p];
pushDown(p, l, r);
int mid = (l+r) >> 1;
long long res = 0;
if(L <= mid) res += query(L, R, l, mid, p << 1);
if(R > mid) res += query(L, R, mid+1, r, p << 1 | 1);
return res;
}
int main(){
int n;
return 0;
}

View File

@@ -0,0 +1,199 @@
# 🌳 Splay Tree — A Self-Adjusting Binary Search Tree
## 1. What is a Splay Tree?
A **Splay Tree** is a type of **self-adjusting Binary Search Tree (BST)**.
Unlike AVL or RedBlack Trees, it **does not maintain explicit balance rules**.
Instead, it follows one simple idea:
> **Every time a node is accessed, move it to the root using rotations.**
This operation is called **splaying**.
As a result, the tree dynamically reorganizes itself based on **access patterns**, not height constraints.
---
## 2. Core Idea: “Access → Root”
In a Splay Tree:
* Searching for a key
* Inserting a node
* Deleting a node
All end with the same operation:
> 🔁 **Splay the accessed node to the root**
This is done via a sequence of rotations:
* **Zig** (single rotation)
* **ZigZig** (double rotation, same direction)
* **ZigZag** (double rotation, opposite directions)
After splaying:
* The accessed node becomes the **root**
* Nodes “related” to it move closer to the top
* Frequently accessed nodes stay shallow
---
## 3. What Makes Splay Trees Special?
### 3.1 No Explicit Balance Condition
Splay Trees:
* Do **not** store height, color, or priority
* Do **not** rebalance on every insert/delete
* May look “unbalanced” at any moment
Yet, surprisingly:
> ✅ **All operations run in amortized (O(\log n)) time**
This is proven using amortized analysis (potential method).
---
### 3.2 Self-Adjusting Behavior
Splay Trees adapt automatically to access patterns:
* Recently accessed nodes become fast to access again
* Sequential access becomes extremely efficient
* “Hot” keys naturally move near the root
This gives Splay Trees several strong properties:
* **Working-set property**
* **Static optimality**
* **Dynamic finger property**
These properties are hard (or impossible) to guarantee with strictly balanced trees.
---
## 4. Splay Tree as an “Easy-to-Pivot” Tree
A very useful way to think about Splay Trees is:
> 🧠 **Any accessed key can instantly become a pivot of the entire tree**
Once a node `x` is splayed to the root:
* All keys `< x` are in the left subtree
* All keys `> x` are in the right subtree
This leads directly to powerful structural operations.
---
## 5. Split and Merge — The Real Power
### 5.1 Split Operation
**Split** divides a tree into two trees based on a key `k`:
* `Left`: all keys `≤ k`
* `Right`: all keys `> k`
In a Splay Tree, this is easy:
1. Search for `k` (or the closest node)
2. Splay it to the root
3. Detach its left or right subtree
Because access automatically moves nodes to the root, **split is almost free**.
---
### 5.2 Merge Operation
**Merge** combines two trees:
* All keys in `Left` are smaller than those in `Right`
Method:
1. Splay the **maximum node** of `Left` to the root
2. Attach `Right` as its right child
Again, splaying makes this simple and efficient.
---
## 6. Interval and Range Manipulation
By combining **split** and **merge**, Splay Trees can easily isolate any interval `[L, R]`:
```text
T
├─ split by R → A , C
└─ split A by L-1 → B , Mid
```
Now:
* `Mid` contains exactly the range `[L, R]`
* You can apply operations to `Mid` only
* Then merge everything back
This makes Splay Trees ideal for:
* Sequence manipulation
* Range updates
* Subarray operations
---
## 7. Typical Applications
### 7.1 Text Editors and Ropes
* Cursor moves locally
* Recent edits are reused
* Cut / paste / reverse ranges efficiently
### 7.2 Dynamic Trees (LinkCut Tree)
* Splay Tree is the **core data structure**
* Supports path queries and updates
* AVL / RedBlack Trees cannot replace it here
### 7.3 Cache-like Access Patterns
* Frequently accessed keys stay near the root
* No need to maintain explicit frequency counters
---
## 8. Comparison with Other BSTs
| Tree Type | Balance Method | Worst Case | Interval Ops | Adaptivity |
| --------- | --------------- | ---------------------- | ------------ | ---------- |
| AVL | Height | (O(\log n)) | Hard | ❌ |
| RedBlack | Color rules | (O(\log n)) | Hard | ❌ |
| Treap | Random priority | (O(\log n)) (expected) | Easy | ❌ |
| **Splay** | Access-based | (O(n)) (single op) | **Easy** | ✅ |
---
## 9. When NOT to Use Splay Trees
Splay Trees are **not ideal** when:
* Strict worst-case latency is required
* Access patterns are uniformly random
* Predictable structure is more important than adaptivity
In such cases, AVL or RedBlack Trees may be better.
---
## 10. One-Sentence Summary
> **A Splay Tree is a self-adjusting BST where any accessed node becomes the root, making the tree extremely easy to split, merge, and reorganize around chosen keys — with strong amortized performance guarantees.**

View File

@@ -0,0 +1,125 @@
#include <iostream>
#include <vector>
using namespace std;
struct Node {
int key;
Node *fa, *ch[2];
int sz;
Node(int k) : key(k), fa(nullptr), sz(1) {
ch[0] = ch[1] = nullptr;
}
};
inline int size(Node* x) {
return x ? x->sz : 0;
}
inline void pull(Node* x) {
if (x)
x->sz = 1 + size(x->ch[0]) + size(x->ch[1]);
}
inline bool is_right(Node* x) {
return x->fa && x->fa->ch[1] == x;
}
void rotate(Node* x) {
Node* p = x->fa;
Node* g = p->fa;
bool dir = (x == p->ch[1]); // 0 = left, 1 = right
// connect x's opposite child to p
p->ch[dir] = x->ch[dir ^ 1];
if (x->ch[dir ^ 1])
x->ch[dir ^ 1]->fa = p;
// move p under x
x->ch[dir ^ 1] = p;
p->fa = x;
// connect x to g
x->fa = g;
if (g) {
if (g->ch[0] == p) g->ch[0] = x;
else g->ch[1] = x;
}
pull(p);
pull(x);
}
void splay(Node* x, Node* goal = nullptr) {
while (x->fa != goal) {
Node* p = x->fa;
Node* g = p->fa;
if (g != goal) {
if ((g->ch[0] == p) == (p->ch[0] == x))
rotate(p); // zig-zig
else
rotate(x); // zig-zag
}
rotate(x);
}
}
/*
* Split by k, result: left <= k, right >= k
*/
void split(Node* root, int k, Node*& left, Node*& right) {
Node* cur = root;
Node* last = nullptr;
while (cur) {
last = cur;
if (k < cur->key)
cur = cur->ch[0];
else
cur = cur->ch[1];
}
if (last)
splay(last);
if (!last) {
left = right = nullptr;
return;
}
if (last->key <= k) {
left = last;
right = last->ch[1];
if (right) right->fa = nullptr;
left->ch[1] = nullptr;
pull(left);
} else {
right = last;
left = last->ch[0];
if (left) left->fa = nullptr;
right->ch[0] = nullptr;
pull(right);
}
}
Node* merge(Node* left, Node* right) {
if (!left) return right;
if (!right) return left;
// find max of left
Node* cur = left;
while (cur->ch[1])
cur = cur->ch[1];
splay(cur); // max becomes root
cur->ch[1] = right;
right->fa = cur;
pull(cur);
return cur;
}
int main(){
return 0;
}

View File

@@ -0,0 +1,67 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
// Split by key: left <= key, right > key
void split(int root, int key, int &x, int &y) {
if (!root) {
x = y = 0;
return;
}
if (tr[root].key <= key) {
x = root;
split(tr[root].right, key, tr[root].right, y);
pushUp(x);
} else {
y = root;
split(tr[root].left, key, x, tr[root].left);
pushUp(y);
}
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].priority > tr[y].priority) {
tr[x].right = merge(tr[x].right, y);
pushUp(x);
return x;
} else {
tr[y].left = merge(x, tr[y].left);
pushUp(y);
return y;
}
}
// Insert key into treap
void insert(int &root, int key) {
int x, y;
split(root, key, x, y);
root = merge(merge(x, newNode(key)), y);
}
int main(){
return 0;
}

View File

@@ -0,0 +1,283 @@
# Treap: A Randomized Balanced Binary Search Tree
## 1. Overview
A **Treap** (Tree + Heap) is a **randomized balanced binary search tree** that simultaneously satisfies:
1. **Binary Search Tree (BST) property** on keys
2. **Heap property** on priorities
By assigning each node a random priority, Treap achieves **expected logarithmic height**, providing efficient dynamic set and sequence operations.
Treaps are widely used in:
* Dynamic ordered sets
* Sequence maintenance
* Range query problems
* Competitive programming as a flexible alternative to AVL / Red-Black trees
---
## 2. Structural Properties
Each Treap node contains:
* `key`: used to maintain BST ordering
* `priority`: a randomly assigned value
* `left`, `right`: child pointers (or indices)
* Optional augmented data (e.g., subtree size, sum)
### 2.1 BST Property
For any node `u`:
* All keys in `u.left` are **less than** `u.key`
* All keys in `u.right` are **greater than** `u.key`
### 2.2 Heap Property
For any node `u`:
* `priority(u)` is **greater than or equal to** the priorities of its children
(Max-heap convention; min-heap also works symmetrically)
---
## 3. Why Randomization Works
If priorities are independent random variables, then:
* The expected height of the Treap is **O(log n)**
* All standard BST operations run in **expected O(log n)** time
This avoids the need for strict rebalancing rules, unlike AVL or Red-Black trees.
---
## 4. Implementation Models
In practice, Treaps are implemented as **pointer-based trees**.
However, for efficiency and memory safety, most implementations use:
> **Array-based node pools with integer indices simulating pointers**
This approach:
* Avoids frequent dynamic memory allocation
* Improves cache locality
* Is standard in algorithmic contexts
---
## 5. Rotating Treap (Classic Treap)
### 5.1 Core Idea
Insertion proceeds as in a normal BST by `key`.
If the heap property is violated after insertion, **tree rotations** are used to restore it.
### 5.2 Operations
* Insert: BST insert + rotations
* Delete: rotate node down until removable
* Search: standard BST search
### 5.3 Characteristics
**Advantages**
* Conceptually close to AVL / Red-Black trees
* Intuitive if rotations are already familiar
**Disadvantages**
* Rotation logic can be error-prone
* Slightly harder to extend for sequence problems
---
## 6. FHQ Treap (Split & Merge Treap)
### 6.1 Core Idea
The FHQ Treap (named after its proposer) eliminates rotations entirely.
It relies on two fundamental operations:
1. **Split**
* Divide a Treap into two based on a key (or position)
2. **Merge**
* Combine two Treaps assuming all keys in the left are smaller
All operations are expressed using **split + merge**, preserving both BST and heap properties automatically.
---
### 6.2 Why FHQ Treap Is Popular
* No explicit rotations
* Cleaner and more modular code
* Ideal for **implicit Treap** (sequence problems)
* Easier to augment with lazy propagation
As a result, FHQ Treap is the **dominant Treap variant** in competitive programming.
---
## 7. Comparison Summary
| Aspect | Rotating Treap | FHQ Treap |
| ---------------- | -------------- | ------------- |
| Balancing method | Rotations | Split & Merge |
| Code complexity | Medium | Low |
| Extensibility | Moderate | Excellent |
| Sequence support | Harder | Natural |
| Contest usage | Less common | Very common |
---
## 8. Rotating Treap — C++ Template
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int root = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
void rotateLeft(int &x) {
int y = tr[x].right;
tr[x].right = tr[y].left;
tr[y].left = x;
pushUp(x);
pushUp(y);
x = y;
}
void rotateRight(int &x) {
int y = tr[x].left;
tr[x].left = tr[y].right;
tr[y].right = x;
pushUp(x);
pushUp(y);
x = y;
}
void insert(int &x, int key) {
if (!x) {
x = newNode(key);
return;
}
if (key < tr[x].key) {
insert(tr[x].left, key);
if (tr[tr[x].left].priority > tr[x].priority)
rotateRight(x);
} else {
insert(tr[x].right, key);
if (tr[tr[x].right].priority > tr[x].priority)
rotateLeft(x);
}
pushUp(x);
}
```
---
## 9. FHQ Treap — C++ Template
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
// Split by key: left <= key, right > key
void split(int root, int key, int &x, int &y) {
if (!root) {
x = y = 0;
return;
}
if (tr[root].key <= key) {
x = root;
split(tr[root].right, key, tr[root].right, y);
pushUp(x);
} else {
y = root;
split(tr[root].left, key, x, tr[root].left);
pushUp(y);
}
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].priority > tr[y].priority) {
tr[x].right = merge(tr[x].right, y);
pushUp(x);
return x;
} else {
tr[y].left = merge(x, tr[y].left);
pushUp(y);
return y;
}
}
// Insert key into treap
void insert(int &root, int key) {
int x, y;
split(root, key, x, y);
root = merge(merge(x, newNode(key)), y);
}
```
---
## 10. Closing Remarks
Treap combines the simplicity of BSTs with the robustness of randomized balancing.
Among its variants, **FHQ Treap** stands out for its elegance, extensibility, and practical usefulness, especially in sequence-based problems.

View File

@@ -0,0 +1,67 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int root = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
void rotateLeft(int &x) {
int y = tr[x].right;
tr[x].right = tr[y].left;
tr[y].left = x;
pushUp(x);
pushUp(y);
x = y;
}
void rotateRight(int &x) {
int y = tr[x].left;
tr[x].left = tr[y].right;
tr[y].right = x;
pushUp(x);
pushUp(y);
x = y;
}
void insert(int &x, int key) {
if (!x) {
x = newNode(key);
return;
}
if (key < tr[x].key) {
insert(tr[x].left, key);
if (tr[tr[x].left].priority > tr[x].priority)
rotateRight(x);
} else {
insert(tr[x].right, key);
if (tr[tr[x].right].priority > tr[x].priority)
rotateLeft(x);
}
pushUp(x);
}
int main(){
return 0;
}

Some files were not shown because too many files have changed in this diff Show More