├── png ├── x.png ├── cpp.png ├── cpp2.png ├── cuda.png ├── llm.png ├── test.png ├── Re_Algo.png ├── because.png ├── bk │ ├── bk1.png │ ├── bk2.png │ └── bk3.png ├── linux.png ├── mysql.png ├── redis.png ├── draw_io_test.png ├── Original long screenshot_1.jpg └── Original long screenshot_2.jpg ├── contest-cpp ├── random │ ├── DP │ │ ├── image-20251216181717422.png │ │ ├── image-20251216181840826.png │ │ └── 树上背包+状态机dp.md │ ├── dfs & bfs.py │ └── 传输协议编码.md └── graphs │ ├── GraphBasics.hpp │ ├── Dinic.hpp │ ├── TreeAlgorithms.hpp │ └── ShortestPaths.hpp ├── Share_the_original_text.md ├── templates ├── 字符串 │ ├── zfunc.cpp │ └── kmp.cpp ├── README.md ├── 数据结构 │ ├── Treearray.cpp │ ├── UnionFind.cpp │ ├── Monotone stack.cpp │ ├── UnionFind_With_Weights.cpp │ ├── Trie.cpp │ └── SegmentTree.cpp └── 图论 │ ├── toposort.cpp │ ├── floyd.cpp │ ├── dijkstra.cpp │ └── Kruscal.cpp ├── bank_transaction ├── v1.0.cpp ├── ARCHITECTURE.md └── include │ ├── thread_pool.hpp │ └── bank.hpp ├── 数位dp.cpp ├── 分块SqrtDecomposition.cpp └── README.md /png/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/x.png -------------------------------------------------------------------------------- /png/cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/cpp.png -------------------------------------------------------------------------------- /png/cpp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/cpp2.png -------------------------------------------------------------------------------- /png/cuda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/cuda.png -------------------------------------------------------------------------------- /png/llm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/llm.png -------------------------------------------------------------------------------- /png/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/test.png -------------------------------------------------------------------------------- /png/Re_Algo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/Re_Algo.png -------------------------------------------------------------------------------- /png/because.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/because.png -------------------------------------------------------------------------------- /png/bk/bk1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/bk/bk1.png -------------------------------------------------------------------------------- /png/bk/bk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/bk/bk2.png -------------------------------------------------------------------------------- /png/bk/bk3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/bk/bk3.png -------------------------------------------------------------------------------- /png/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/linux.png -------------------------------------------------------------------------------- /png/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/mysql.png -------------------------------------------------------------------------------- /png/redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/redis.png -------------------------------------------------------------------------------- /png/draw_io_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/draw_io_test.png -------------------------------------------------------------------------------- /png/Original long screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/Original long screenshot_1.jpg -------------------------------------------------------------------------------- /png/Original long screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/png/Original long screenshot_2.jpg -------------------------------------------------------------------------------- /contest-cpp/random/DP/image-20251216181717422.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/contest-cpp/random/DP/image-20251216181717422.png -------------------------------------------------------------------------------- /contest-cpp/random/DP/image-20251216181840826.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvy010/Algo-Atlas/HEAD/contest-cpp/random/DP/image-20251216181840826.png -------------------------------------------------------------------------------- /Share_the_original_text.md: -------------------------------------------------------------------------------- 1 | ![0](./png/because.png) 2 | 原文长截图备份 3 | ![1](./png/Original%20long%20screenshot_1.jpg) 4 | ![2](./png/Original%20long%20screenshot_2.jpg) -------------------------------------------------------------------------------- /templates/字符串/zfunc.cpp: -------------------------------------------------------------------------------- 1 | vector zfunc(const string& s) { 2 | int n = s.size(); 3 | vector z(n); 4 | int l = 0, r = 0; 5 | for (int i = 1; i < n; i++) { 6 | if (i <= r) z[i] = min(z[i - l], r - i + 1); 7 | while (i + z[i] < n && s[z[i]] == s[i + z[i]]) { 8 | l = i, r = i + z[i]; 9 | z[i]++; 10 | } 11 | } 12 | z[0] = n; 13 | return z; 14 | } -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | ## 打算法竞赛用的板子 2 | 3 | 作者还是菜鸡,因此没有收录太多板子,还在学习中,目前目录如下: 4 | 5 | ### 图论 6 | - ``Kruscal.cpp``:Kruscal算法,求MST。 7 | - ``Dijsktra.cpp``:Dijkstra算法,单源最短路。 8 | - ``Floyd.cpp``:Floyd算法,全源最短路。 9 | - ``toposort.cpp``:拓扑排序 10 | 11 | ### 数据结构 12 | - ``Monotone Stack``:单调栈 13 | - ``UnionFind.cpp``:并查集(带大小) 14 | - ``UnionFind_With_Weights.cpp``:带权并查集 15 | - ``Trie.cpp``:Trie/字典树,前缀匹配 16 | - ``Treearray``:树状数组,区间修改单点查询,离散化。 17 | - ``SegmentTree``:线段树,区间修改单点查询,线段树二分。 18 | 19 | ### 字符串 20 | - ``kmp.cpp``:KMP算法,求border。 21 | - ``zfunc.cpp``:Zfunc/拓展kmp算法,求LCP。 -------------------------------------------------------------------------------- /templates/数据结构/Treearray.cpp: -------------------------------------------------------------------------------- 1 | class Tree { 2 | vector tree; 3 | public: 4 | Tree(int n) : tree(n) {} 5 | void add(int i, int val) { 6 | for (int x = i; x < tree.size(); x += lowbit(x)) tree[x] += val; 7 | return; 8 | } 9 | int pre(int x) { 10 | int res = 0; 11 | for (; x > 0; x -= lowbit(x)) res += tree[x]; 12 | return res; 13 | } 14 | }; 15 | 16 | //离散化 17 | 18 | auto sorted = nums; 19 | ranges::sort(sorted); 20 | sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end()); 21 | int m = sorted.size(); 22 | int tem1 = ranges::lower_bound(sorted, nums[0]) - sorted.begin() + 1; -------------------------------------------------------------------------------- /templates/图论/toposort.cpp: -------------------------------------------------------------------------------- 1 | vector toposort(int n, vector> edges) { 2 | vector> ma(n); 3 | vector deg(n); 4 | for (auto p : edges) { 5 | ma[p[0]].push_back(p[1]); 6 | ma[p[1]].push_back(p[0]); 7 | } 8 | queue q; 9 | for (int i = 0; i <= n - 1; i++) { 10 | if (deg[i] == 0) q.push(i); 11 | } 12 | vector res; 13 | while (!q.empty()) { 14 | auto node = q.front(); 15 | q.pop(); 16 | res.push_back(node); 17 | for (int p : ma[node]) { 18 | if (--deg[p] == 0) q.push(p); 19 | } 20 | } 21 | return res; 22 | } -------------------------------------------------------------------------------- /bank_transaction/v1.0.cpp: -------------------------------------------------------------------------------- 1 | using namespace std; 2 | 3 | class Bank { 4 | vector b; 5 | bool ok(int a){ return 1 <= a && a <= (int)b.size(); } 6 | public: 7 | Bank(vector& balance): b(balance) {} 8 | bool transfer(int a, int c, long long m){ 9 | if(!ok(a) || !ok(c) || b[a-1] < m) return false; 10 | b[a-1] -= m; b[c-1] += m; return true; 11 | } 12 | bool deposit(int a, long long m){ 13 | if(!ok(a)) return false; 14 | b[a-1] += m; return true; 15 | } 16 | bool withdraw(int a, long long m){ 17 | if(!ok(a) || b[a-1] < m) return false; 18 | b[a-1] -= m; return true; 19 | } 20 | }; -------------------------------------------------------------------------------- /templates/图论/floyd.cpp: -------------------------------------------------------------------------------- 1 | vector> floyd(int n, vector> edges) { 2 | vector> dp(n, vector(n, LLONG_MAX / 2)); 3 | for (int i = 0; i <= n - 1; i++) dp[i][i] = 0; 4 | for (auto p : edges) { 5 | dp[p[0]][p[1]] = min(dp[p[0]][p[1]], 1LL * p[2]); 6 | dp[p[1]][p[0]] = min(dp[p[1]][p[0]], 1LL * p[2]); 7 | } 8 | for (int k = 0; k <= n - 1; k++) { 9 | for (int i = 0; i <= n - 1; i++) { 10 | if (dp[i][k] == LLONG_MAX / 2) continue; 11 | for (int j = 0; j <= n - 1; j++) { 12 | dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]); 13 | } 14 | } 15 | } 16 | return dp; 17 | } -------------------------------------------------------------------------------- /templates/数据结构/UnionFind.cpp: -------------------------------------------------------------------------------- 1 | class UnionFind { 2 | vector fa; 3 | vector sz; // 集合大小 4 | public: 5 | int cc; // 连通块个数 6 | UnionFind(int n) : fa(n), sz(n, 1), cc(n) { ranges::iota(fa, 0); } 7 | int get(int x) { 8 | if (fa[x] != x) fa[x] = get(fa[x]); 9 | return fa[x]; 10 | } 11 | bool is_same(int x, int y) { return get(x) == get(y); } 12 | bool merge(int from, int to) { 13 | int x = get(from), y = get(to); 14 | if (x == y) return false; 15 | fa[x] = y; 16 | sz[y] += sz[x]; 17 | cc--; 18 | return true; 19 | } 20 | int get_size(int x) { // 查询x所在集合大小 21 | return sz[get(x)]; 22 | } 23 | }; -------------------------------------------------------------------------------- /templates/数据结构/Monotone stack.cpp: -------------------------------------------------------------------------------- 1 | int largestRectangleArea(vector& heights) { 2 | int n = heights.size(); 3 | vector r(n, n); 4 | vector l(n, -1); 5 | stack s; 6 | for (int i = n - 1; i >= 0; i--) { 7 | while (!s.empty() && heights[s.top()] >= heights[i]) s.pop(); 8 | if (!s.empty()) r[i] = s.top(); 9 | s.push(i); 10 | } 11 | while (!s.empty()) s.pop(); 12 | for (int i = 0; i <= n - 1; i++) { 13 | while (!s.empty() && heights[s.top()] >= heights[i]) s.pop(); 14 | if (!s.empty()) l[i] = s.top(); 15 | s.push(i); 16 | } 17 | int maxx = INT_MIN; 18 | for (int i = 0; i <= n - 1; i++) maxx = max(maxx, heights[i] * (r[i] - l[i] - 1)); 19 | return maxx; 20 | } -------------------------------------------------------------------------------- /templates/字符串/kmp.cpp: -------------------------------------------------------------------------------- 1 | vector kmp(const string& text, const string& pattern) { 2 | int m = pattern.size(); 3 | vector pi(m); 4 | int cnt = 0; 5 | for (int i = 1; i < m; i++) { 6 | char b = pattern[i]; 7 | while (cnt && pattern[cnt] != b) cnt = pi[cnt - 1]; 8 | if (pattern[cnt] == b) cnt++; 9 | pi[i] = cnt; 10 | } 11 | vector res; 12 | cnt = 0; 13 | for (int i = 0; i < text.size(); i++) { 14 | char b = text[i]; 15 | while (cnt && pattern[cnt] != b) { 16 | cnt = pi[cnt - 1]; 17 | } 18 | if (pattern[cnt] == b) cnt++; 19 | if (cnt == m) { 20 | res.push_back(i - m + 1); 21 | cnt = pi[cnt - 1]; 22 | } 23 | } 24 | return res; 25 | } -------------------------------------------------------------------------------- /templates/图论/dijkstra.cpp: -------------------------------------------------------------------------------- 1 | vector dij(int n, vector> edges, int st) { 2 | vector>> ma(n); 3 | for (auto p : edges) { 4 | ma[p[0]].emplace_back(p[1], p[2]); 5 | ma[p[1]].emplace_back(p[0], p[2]); 6 | } 7 | vector dis(n, LLONG_MAX); 8 | priority_queue, vector>, greater<>> q; 9 | dis[st] = 0; 10 | q.emplace(0, st); 11 | while (!q.empty()) { 12 | auto [dis_x, x] = q.top(); 13 | q.pop(); 14 | if (dis_x > dis[x]) continue; 15 | for (auto [y, z] : ma[x]) { 16 | ll new_y = dis_x + z; 17 | if (new_y < dis[y]) { 18 | dis[y] = new_y; 19 | q.emplace(new_y, y); 20 | } 21 | } 22 | } 23 | return dis; 24 | } -------------------------------------------------------------------------------- /templates/图论/Kruscal.cpp: -------------------------------------------------------------------------------- 1 | class UnionFind { 2 | vector fa; 3 | vector sz; 4 | 5 | public: 6 | int cc; 7 | UnionFind(int n) : fa(n), cc(n), sz(n, 1) { 8 | for (int i = 0; i <= n - 1; i++) fa[i] = i; 9 | } 10 | int find(int x) { 11 | if (fa[x] == x) return x; 12 | return fa[x] = find(fa[x]); 13 | } 14 | bool merge(int x, int y) { 15 | int dx = find(x), dy = find(y); 16 | if (dx == dy) return false; 17 | fa[dx] = dy; 18 | sz[dy] += sz[x]; 19 | cc--; 20 | return true; 21 | } 22 | int get_size(int x) { return sz[find(x)]; } 23 | }; 24 | 25 | ll Kruskal(int n, vector> edges) { 26 | sort(edges.begin(), edges.end(), [&](const vector& x, const vector& y) { return x[2] < y[2]; }); 27 | UnionFind u(n); 28 | ll ans = 0; 29 | for (auto p : edges) { 30 | if (u.merge(p[0], p[1])) { 31 | ans += 1LL * p[2]; 32 | } 33 | } 34 | if (u.cc > 1) return -1; 35 | return ans; 36 | } -------------------------------------------------------------------------------- /templates/数据结构/UnionFind_With_Weights.cpp: -------------------------------------------------------------------------------- 1 | template 2 | class UnionFind { 3 | public: 4 | vector fa; 5 | vector dis; // 表示x到x所在集合的代表元的距离 6 | 7 | UnionFind(int n) : fa(n), dis(n) { 8 | for (int i = 0; i <= n - 1; i++) fa[i] = i; 9 | } 10 | 11 | int get(int x) { 12 | if (fa[x] != x) { 13 | int root = get(fa[x]); 14 | dis[x] += dis[fa[x]]; // 递归更新x到其代表元的距离 15 | fa[x] = root; 16 | } 17 | return fa[x]; 18 | } 19 | 20 | bool same(int x, int y) { return get(x) == get(y); } 21 | 22 | // 计算从from到to的相对距离,需要它们在同一个集合中 23 | T get_relative_distance(int from, int to) { 24 | get(from), get(to); 25 | return dis[from] - dis[to]; 26 | } 27 | 28 | // 合并from和to,新增信息to-from=value 29 | // 若to和from不在一个集合,则返回true,否则返回是否与当前信息矛盾 30 | bool merge(int from, int to, T value) { 31 | int x = get(from), y = get(to); 32 | if (x == y) return dis[from] - dis[to] == value; 33 | dis[x] = value + dis[to] - dis[from]; // 更新代表元之间的距离 34 | fa[x] = y; 35 | return true; 36 | } 37 | }; -------------------------------------------------------------------------------- /contest-cpp/random/dfs & bfs.py: -------------------------------------------------------------------------------- 1 | #lc2059 minimum operations to convert number 2 | class Solution: 3 | #刚拿到这题找不出什么规律,要我们求最小运算次数,加之这个数据范围很小,无权最短路...暴力... 嘶~ BFS ?! 4 | 5 | #以前自己做题经常爱用 DFS,因为递归代码一般会少一些。 6 | # 但是 DFS 与 BFS 在寻找路径最不同的一点是,DFS 是一条路走到黑,它适合用于 「是否存在一条路径」 7 | # 而 BFS 是影分身,每次影分身都会在原基础上走一步,适合用于 「是否存在一条最短路径」。 8 | 9 | #明白上面两点区别,便可顺利通过。 10 | 11 | def minimumOperations(self, nums: List[int], start: int, goal: int) -> int: 12 | if start < 0 or start > 1000 or start == goal: 13 | return start == goal 14 | q = deque([start]) 15 | vis = set([start]) 16 | step = 0 17 | 18 | while q: 19 | size = len(q) 20 | while size: 21 | cur = q.popleft() 22 | for x in nums: 23 | compute = (cur + x, cur - x, cur ^ x) 24 | for y in compute: 25 | if y == goal: 26 | return step + 1 27 | if 0 <= y <= 1000 and y not in vis: 28 | q.append(y) 29 | vis.add(y) 30 | size -= 1 31 | step += 1 32 | 33 | return -1 -------------------------------------------------------------------------------- /templates/数据结构/Trie.cpp: -------------------------------------------------------------------------------- 1 | struct Node { 2 | Node* son[26]{}; 3 | bool end = false; 4 | }; 5 | class Trie { 6 | Node* root = new Node(); 7 | 8 | int find(string word) { 9 | Node* cur = root; 10 | for (char c : word) { 11 | c -= 'a'; 12 | if (!cur->son[c]) return 0; 13 | cur = cur->son[c]; 14 | } 15 | return cur->end ? 2 : 1; 16 | } 17 | string findpre(string word) { 18 | Node* cur = root; 19 | int cnt = 0; 20 | for (char c : word) { 21 | if (cur->end) return word.substr(0, cnt); 22 | c -= 'a'; 23 | cnt++; 24 | if (!cur->son[c]) return ""; 25 | cur = cur->son[c]; 26 | } 27 | return ""; 28 | } 29 | void destory(Node* node) { 30 | if (!node) return; 31 | for (Node* son : node->son) destory(son); 32 | delete node; 33 | } 34 | 35 | public: 36 | // 析构函数 37 | ~Trie() { destory(root); } 38 | 39 | // 往trie中插入word 40 | void insert(string word) { 41 | Node* cur = root; 42 | for (char c : word) { 43 | c -= 'a'; 44 | if (!cur->son[c]) cur->son[c] = new Node(); 45 | cur = cur->son[c]; 46 | } 47 | cur->end = true; 48 | } 49 | 50 | // 查询trie中是否有word的前缀,若有返回,若无返回空串 51 | string pre(string word) { return findpre(word); } 52 | 53 | // 查询是否存在与word相等的字符串 54 | bool search(string word) { return find(word) == 2; } 55 | 56 | // 查询是否存在以prefix为前缀的字符串 57 | bool startsWith(string prefix) { return find(prefix) != 0; } 58 | }; -------------------------------------------------------------------------------- /bank_transaction/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | ## 银行交易系统(高并发 C++ 架构设计) 2 | 3 | ### 目标 4 | - 高并发、多线程下安全处理账户的存取款与转账交易。 5 | - 保证数据一致性(余额不丢失、不重复扣减、不出现负数越界)。 6 | - 提供良好的可扩展性(线程池扩展、事务批量处理、异步化)。 7 | 8 | ### 总体架构 9 | - 线程池(ThreadPool) 10 | - 固定数量工作线程,阻塞队列提交任务,`submit` 返回 `std::future`。 11 | - 任务异常安全:在线程内捕获,传播到 future。 12 | - 并发账户模型(Bank) 13 | - 使用账户级细粒度互斥锁(每个账户一个 `std::mutex`)。 14 | - 存取款:持有该账户的独占锁;转账:对两个账户“按序加锁”避免死锁。 15 | - 余额使用 `long long` 存储,所有修改在互斥锁保护下进行。 16 | - 事务模型(Transaction) 17 | - 三类:Deposit、Withdraw、Transfer。 18 | - 携带唯一 ID、源/目标账户、金额。 19 | - 结果(TransactionResult)包含 ID、是否成功、错误信息等。 20 | - 事务处理器(TransactionProcessor) 21 | - 将事务批量提交到线程池并发执行。 22 | - 聚合执行结果,保证与输入顺序无关但可按 ID 关联。 23 | 24 | ### 并发控制与一致性 25 | - 账户级锁粒度: 26 | - 单账户操作(存/取):持有单锁,临界区尽量小。 27 | - 两账户操作(转账):总是先锁定较小账户 ID,再锁定较大账户 ID,避免死锁。 28 | - 资金校验: 29 | - 取款与转账需校验余额是否足够;不足则失败并不修改状态。 30 | - 存款允许任意非负金额(示例中约束金额 > 0)。 31 | - 原子性: 32 | - 每笔事务在其临界区内要么全部成功,要么失败不改状态。 33 | 34 | ### 可扩展性考量 35 | - 线程池大小: 36 | - CPU 密集型任务:≈ CPU 核数。 37 | - I/O 或混合型:可适当放大(2x~4x)。 38 | - 分区与扩展(如有超大规模账户): 39 | - 可按账户 ID 进行分片(Sharding),每片内独立锁。跨片转账可用“两阶段锁”或消息中间件。 40 | - 多机部署(超出本示例范围): 41 | - 使用分布式事务或最终一致(事件驱动)。 42 | 43 | ### 异常与容错 44 | - 线程任务异常被捕获并通过 `std::future` 传播到调用端。 45 | - 对业务失败(余额不足、账户不存在)以业务错误返回,不抛异常。 46 | 47 | ### 性能权衡 48 | - 账户级锁优点:锁粒度小、冲突少;缺点:转账需双锁。 49 | - 避免使用全局大锁(吞吐低)。 50 | - 不采用 `std::atomic` 直接更新余额,因为转账需要跨两个账户的复合不变量,必须使用互斥保证一致性。 51 | 52 | ### 数据校验与账实相符 53 | - 可在批处理前记录总余额 `S0`,统计净现金流 `Δ`(存款+,取款-),最终核对 `S1 == S0 + Δ`。 54 | - 转账对总余额无影响。 55 | 56 | ### 目录结构(计划) 57 | ``` 58 | bank_transaction/ 59 | ├─ include/ 60 | │ ├─ thread_pool.hpp 61 | │ ├─ bank.hpp 62 | │ ├─ transaction.hpp 63 | │ └─ transaction_processor.hpp 64 | ├─ src/ 65 | │ └─ main.cpp 66 | ├─ v1.0.cpp # 原始简单实现(单线程示例) 67 | ├─ CMakeLists.txt 68 | ├─ README.md 69 | └─ ARCHITECTURE.md 70 | ``` 71 | 72 | ### 测试思路 73 | - 随机生成大量事务(混合存取款与转账),并发执行。 74 | - 统计期望净现金流并校对最终余额总和。 75 | - 在高线程数与高冲突(同一账户热度高)的情况下观察吞吐与延迟。 76 | 77 | 78 | -------------------------------------------------------------------------------- /contest-cpp/graphs/GraphBasics.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace contest { 7 | 8 | struct Graph { 9 | int n = 0; 10 | std::vector> adj; 11 | bool directed = false; 12 | 13 | Graph() = default; 14 | Graph(int n_, bool directed_ = false) { init(n_, directed_); } 15 | 16 | void init(int n_, bool directed_) { 17 | n = n_; 18 | directed = directed_; 19 | adj.assign(n, {}); 20 | } 21 | 22 | void addEdge(int u, int v) { 23 | adj[u].push_back(v); 24 | if (!directed) adj[v].push_back(u); 25 | } 26 | 27 | std::vector bfs(int source) const { 28 | std::vector dist(n, -1); 29 | std::queue q; 30 | dist[source] = 0; 31 | q.push(source); 32 | while (!q.empty()) { 33 | int u = q.front(); 34 | q.pop(); 35 | for (int v : adj[u]) if (dist[v] == -1) { 36 | dist[v] = dist[u] + 1; 37 | q.push(v); 38 | } 39 | } 40 | return dist; 41 | } 42 | 43 | std::vector multiSourceBfs(const std::vector& sources) const { 44 | std::vector dist(n, -1); 45 | std::queue q; 46 | for (int s : sources) { 47 | dist[s] = 0; 48 | q.push(s); 49 | } 50 | while (!q.empty()) { 51 | int u = q.front(); 52 | q.pop(); 53 | for (int v : adj[u]) if (dist[v] == -1) { 54 | dist[v] = dist[u] + 1; 55 | q.push(v); 56 | } 57 | } 58 | return dist; 59 | } 60 | 61 | std::vector topoSort() const { 62 | std::vector indeg(n); 63 | for (int u = 0; u < n; ++u) for (int v : adj[u]) ++indeg[v]; 64 | std::queue q; 65 | for (int i = 0; i < n; ++i) if (indeg[i] == 0) q.push(i); 66 | std::vector order; 67 | while (!q.empty()) { 68 | int u = q.front(); 69 | q.pop(); 70 | order.push_back(u); 71 | for (int v : adj[u]) { 72 | if (--indeg[v] == 0) q.push(v); 73 | } 74 | } 75 | if (static_cast(order.size()) != n) order.clear(); 76 | return order; 77 | } 78 | }; 79 | 80 | } // namespace contest 81 | 82 | -------------------------------------------------------------------------------- /contest-cpp/graphs/Dinic.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace contest { 9 | 10 | struct Dinic { 11 | struct Edge { 12 | int to; 13 | int rev; 14 | long long cap; 15 | }; 16 | 17 | int n = 0; 18 | std::vector> g; 19 | std::vector level, ptr; 20 | 21 | Dinic() = default; 22 | explicit Dinic(int n_) { init(n_); } 23 | 24 | void init(int n_) { 25 | n = n_; 26 | g.assign(n, {}); 27 | level.resize(n); 28 | ptr.resize(n); 29 | } 30 | 31 | void addEdge(int u, int v, long long cap, bool directed = true) { 32 | Edge a{v, static_cast(g[v].size()), cap}; 33 | Edge b{u, static_cast(g[u].size()), directed ? 0 : cap}; 34 | g[u].push_back(a); 35 | g[v].push_back(b); 36 | } 37 | 38 | long long maxFlow(int s, int t) { 39 | const long long INF = std::numeric_limits::max(); 40 | long long flow = 0; 41 | while (bfs(s, t)) { 42 | std::fill(ptr.begin(), ptr.end(), 0); 43 | while (long long pushed = dfs(s, t, INF)) flow += pushed; 44 | } 45 | return flow; 46 | } 47 | 48 | private: 49 | bool bfs(int s, int t) { 50 | std::fill(level.begin(), level.end(), -1); 51 | std::queue q; 52 | level[s] = 0; 53 | q.push(s); 54 | while (!q.empty()) { 55 | int v = q.front(); 56 | q.pop(); 57 | for (const auto& e : g[v]) if (e.cap > 0 && level[e.to] == -1) { 58 | level[e.to] = level[v] + 1; 59 | q.push(e.to); 60 | } 61 | } 62 | return level[t] != -1; 63 | } 64 | 65 | long long dfs(int v, int t, long long pushed) { 66 | if (v == t || pushed == 0) return pushed; 67 | for (int& i = ptr[v]; i < static_cast(g[v].size()); ++i) { 68 | Edge& e = g[v][i]; 69 | if (e.cap == 0 || level[e.to] != level[v] + 1) continue; 70 | long long tr = dfs(e.to, t, std::min(pushed, e.cap)); 71 | if (tr == 0) continue; 72 | e.cap -= tr; 73 | g[e.to][e.rev].cap += tr; 74 | return tr; 75 | } 76 | return 0; 77 | } 78 | }; 79 | 80 | } 81 | 82 | -------------------------------------------------------------------------------- /contest-cpp/graphs/TreeAlgorithms.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace contest { 8 | 9 | struct TreeAlgorithms { 10 | int n = 0; 11 | int LOG = 0; 12 | std::vector> adj; 13 | std::vector> up; 14 | std::vector depth; 15 | std::vector tin, tout; 16 | int timer = 0; 17 | 18 | TreeAlgorithms() = default; 19 | explicit TreeAlgorithms(int n_) { init(n_); } 20 | 21 | void init(int n_) { 22 | n = n_; 23 | LOG = 1; 24 | while ((1 << LOG) <= n) ++LOG; 25 | adj.assign(n, {}); 26 | up.assign(LOG, std::vector(n, -1)); 27 | depth.assign(n, 0); 28 | tin.assign(n, 0); 29 | tout.assign(n, 0); 30 | timer = 0; 31 | } 32 | 33 | void addEdge(int u, int v) { 34 | adj[u].push_back(v); 35 | adj[v].push_back(u); 36 | } 37 | 38 | void build(int root = 0) { 39 | dfs(root, root); 40 | } 41 | 42 | int lca(int u, int v) const { 43 | if (isAncestor(u, v)) return u; 44 | if (isAncestor(v, u)) return v; 45 | for (int k = LOG - 1; k >= 0; --k) { 46 | if (!isAncestor(up[k][u], v)) u = up[k][u]; 47 | } 48 | return up[0][u]; 49 | } 50 | 51 | int distance(int u, int v) const { 52 | int w = lca(u, v); 53 | return depth[u] + depth[v] - 2 * depth[w]; 54 | } 55 | 56 | std::pair diameterEndpoints() const { 57 | auto dist0 = bfs(0); 58 | int u = 0; 59 | for (int i = 0; i < n; ++i) if (dist0[i] > dist0[u]) u = i; 60 | auto distU = bfs(u); 61 | int v = u; 62 | for (int i = 0; i < n; ++i) if (distU[i] > distU[v]) v = i; 63 | return {u, v}; 64 | } 65 | 66 | private: 67 | void dfs(int u, int p) { 68 | tin[u] = ++timer; 69 | up[0][u] = p; 70 | for (int k = 1; k < LOG; ++k) up[k][u] = up[k - 1][up[k - 1][u]]; 71 | for (int v : adj[u]) if (v != p) { 72 | depth[v] = depth[u] + 1; 73 | dfs(v, u); 74 | } 75 | tout[u] = ++timer; 76 | } 77 | 78 | bool isAncestor(int u, int v) const { 79 | return tin[u] <= tin[v] && tout[v] <= tout[u]; 80 | } 81 | 82 | std::vector bfs(int source) const { 83 | std::vector dist(n, -1); 84 | std::queue q; 85 | dist[source] = 0; 86 | q.push(source); 87 | while (!q.empty()) { 88 | int u = q.front(); 89 | q.pop(); 90 | for (int v : adj[u]) if (dist[v] == -1) { 91 | dist[v] = dist[u] + 1; 92 | q.push(v); 93 | } 94 | } 95 | return dist; 96 | } 97 | }; 98 | 99 | } // namespace contest 100 | 101 | -------------------------------------------------------------------------------- /templates/数据结构/SegmentTree.cpp: -------------------------------------------------------------------------------- 1 | template 2 | class SegmentTree { 3 | int n; 4 | vector tree; 5 | T merge_val(T a, T b) const { return max(a, b); } // 合并子树 6 | 7 | void maintain(int node) { // 维护整棵树 8 | tree[node] = merge_val(tree[node * 2], tree[node * 2 + 1]); 9 | } 10 | 11 | void build(const vector& a, int node, int l, int r) { 12 | if (l == r) { 13 | tree[node] = a[l]; 14 | return; 15 | } 16 | int m = (l + r) / 2; 17 | build(a, node * 2, l, m); 18 | build(a, node * 2 + 1, m + 1, r); 19 | maintain(node); 20 | } // 建树 21 | 22 | void update(int node, int l, int r, int i, T val) { 23 | if (l == r) { 24 | tree[node] = val; 25 | return; 26 | } 27 | int m = (l + r) / 2; 28 | if (i <= m) 29 | update(node * 2, l, m, i, val); 30 | else 31 | update(node * 2 + 1, m + 1, r, i, val); 32 | maintain(node); 33 | } // 更新i处的值为val 34 | 35 | T query(int node, int l, int r, int ql, int qr) const { 36 | if (ql <= l && r <= qr) return tree[node]; 37 | int m = (l + r) / 2; 38 | if (qr <= m) return query(node * 2, l, m, ql, qr); 39 | if (ql > m) return query(node * 2 + 1, m + 1, r, ql, qr); 40 | T l_res = query(node * 2, l, m, ql, qr); 41 | T r_res = query(node * 2 + 1, m + 1, r, ql, qr); 42 | return merge_val(l_res, r_res); 43 | } // 查询[ql,qr]的值 44 | 45 | public: 46 | SegmentTree(int n, T init_val) : SegmentTree(vector(n, init_val)) {} 47 | 48 | SegmentTree(const vector& a) 49 | : n(a.size()), tree(2 << bit_width(a.size() - 1)) { 50 | build(a, 1, 0, n - 1); 51 | } // 传入一个数组维护 52 | 53 | void update(int i, T val) { update(1, 0, n - 1, i, val); } // 更新i的值为val 54 | 55 | T query(int ql, int qr) const { return query(1, 0, n - 1, ql, qr); } // 查询[ql,qr]的值 56 | 57 | T get(int i) const { return query(1, 0, n - 1, i, i); } // 取出i处的值 58 | 59 | T find(int val, int node, int l, int r) { 60 | if (tree[node] < val) return -1; 61 | if (l == r) { 62 | tree[node] = -1; 63 | return l; 64 | } 65 | int mid = (l + r) / 2; 66 | int res = find(val, node * 2, l, mid); 67 | if (res < 0) res = find(val, node * 2 + 1, mid + 1, r); 68 | maintain(node); 69 | return res; 70 | } // 线段树二分,查询最早的大于等于val的下标 71 | 72 | 73 | }; 74 | 75 | int find(int val, int node, int l, int r, int qr) { 76 | if (M - tree[node].first < val) return -1; 77 | if (l > qr) return -1; 78 | if (l == r) return l; 79 | int mid = (l + r) / 2; 80 | if (M - tree[node * 2].first >= val) return find(val, node * 2, l, mid, qr); 81 | if (qr > mid) return find(val, node * 2 + 1, mid + 1, r, qr); 82 | return -1; 83 | } // 线段树二分,查询最早的大于等于val的下标 -------------------------------------------------------------------------------- /bank_transaction/include/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | // 线程池:固定线程数,支持 submit(lambda) 返回 std::future 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class ThreadPool { 15 | public: 16 | explicit ThreadPool(std::size_t numThreads) { 17 | if (numThreads == 0) { 18 | numThreads = 1; 19 | } 20 | try { 21 | for (std::size_t i = 0; i < numThreads; ++i) { 22 | workers.emplace_back([this]{ this->workerLoop(); }); 23 | } 24 | } catch (...) { 25 | stop(); 26 | throw; 27 | } 28 | } 29 | 30 | ThreadPool(const ThreadPool&) = delete; 31 | ThreadPool& operator=(const ThreadPool&) = delete; 32 | 33 | ~ThreadPool() { 34 | stop(); 35 | } 36 | 37 | template , std::decay_t...>> 39 | std::future submit(F&& f, Args&&... args) { 40 | auto taskPtr = std::make_shared>( 41 | std::bind(std::forward(f), std::forward(args)...) 42 | ); 43 | 44 | std::future fut = taskPtr->get_future(); 45 | { 46 | std::unique_lock lk(mtx); 47 | if (isStopping) { 48 | throw std::runtime_error("ThreadPool stopped"); 49 | } 50 | tasks.emplace([taskPtr]{ 51 | try { (*taskPtr)(); } catch (...) { throw; } 52 | }); 53 | } 54 | cv.notify_one(); 55 | return fut; 56 | } 57 | 58 | void stop() noexcept { 59 | { 60 | std::unique_lock lk(mtx); 61 | if (isStopping) return; 62 | isStopping = true; 63 | } 64 | cv.notify_all(); 65 | for (std::thread& t : workers) { 66 | if (t.joinable()) t.join(); 67 | } 68 | workers.clear(); 69 | } 70 | 71 | private: 72 | void workerLoop() { 73 | while (true) { 74 | std::function task; 75 | { 76 | std::unique_lock lk(mtx); 77 | cv.wait(lk, [this]{ return isStopping || !tasks.empty(); }); 78 | if (isStopping && tasks.empty()) return; 79 | task = std::move(tasks.front()); 80 | tasks.pop(); 81 | } 82 | try { 83 | task(); 84 | } catch (...) { 85 | // 任务异常在 future 中可见,这里不吞异常 86 | } 87 | } 88 | } 89 | 90 | std::vector workers; 91 | std::queue> tasks; 92 | std::mutex mtx; 93 | std::condition_variable cv; 94 | bool isStopping {false}; 95 | }; 96 | 97 | 98 | -------------------------------------------------------------------------------- /bank_transaction/include/bank.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class ConcurrentBank { 11 | public: 12 | explicit ConcurrentBank(std::vector initialBalances) 13 | : balances(std::move(initialBalances)), accountMutexes(balances.size()) {} 14 | 15 | std::size_t numAccounts() const noexcept { return balances.size(); } 16 | 17 | bool deposit(std::size_t accountIndex, long long amount) { 18 | if (!isValidAccount(accountIndex) || amount <= 0) return false; 19 | std::lock_guard g(accountMutexes[accountIndex]); 20 | balances[accountIndex] += amount; 21 | return true; 22 | } 23 | 24 | bool withdraw(std::size_t accountIndex, long long amount) { 25 | if (!isValidAccount(accountIndex) || amount <= 0) return false; 26 | std::lock_guard g(accountMutexes[accountIndex]); 27 | if (balances[accountIndex] < amount) return false; 28 | balances[accountIndex] -= amount; 29 | return true; 30 | } 31 | 32 | bool transfer(std::size_t fromIndex, std::size_t toIndex, long long amount) { 33 | if (amount <= 0) return false; 34 | if (!isValidAccount(fromIndex) || !isValidAccount(toIndex)) return false; 35 | if (fromIndex == toIndex) return true; // 自转账视为无操作 36 | 37 | std::size_t a = std::min(fromIndex, toIndex); 38 | std::size_t b = std::max(fromIndex, toIndex); 39 | std::unique_lock lockA(accountMutexes[a]); 40 | std::unique_lock lockB(accountMutexes[b]); 41 | 42 | if (fromIndex == a) { 43 | if (balances[fromIndex] < amount) return false; 44 | balances[fromIndex] -= amount; 45 | balances[toIndex] += amount; 46 | } else { 47 | if (balances[fromIndex] < amount) return false; 48 | balances[fromIndex] -= amount; 49 | balances[toIndex] += amount; 50 | } 51 | return true; 52 | } 53 | 54 | long long getBalance(std::size_t accountIndex) const { 55 | if (!isValidAccount(accountIndex)) throw std::out_of_range("bad account"); 56 | std::lock_guard g(accountMutexes[accountIndex]); 57 | return balances[accountIndex]; 58 | } 59 | 60 | long long sumBalances() const { 61 | // 顺序加锁,避免死锁 62 | for (std::size_t i = 0; i < accountMutexes.size(); ++i) { 63 | accountMutexes[i].lock(); 64 | } 65 | long long s = 0; 66 | for (long long v : balances) s += v; 67 | for (std::size_t i = accountMutexes.size(); i-- > 0;) { 68 | accountMutexes[i].unlock(); 69 | } 70 | return s; 71 | } 72 | 73 | private: 74 | bool isValidAccount(std::size_t accountIndex) const noexcept { 75 | return accountIndex < balances.size(); 76 | } 77 | 78 | mutable std::vector accountMutexes; 79 | std::vector balances; 80 | }; 81 | 82 | 83 | -------------------------------------------------------------------------------- /contest-cpp/graphs/ShortestPaths.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace contest { 12 | 13 | struct WeightedGraph { 14 | struct Edge { 15 | int to; 16 | long long w; 17 | }; 18 | 19 | int n = 0; 20 | bool directed = false; 21 | std::vector> adj; 22 | 23 | WeightedGraph() = default; 24 | WeightedGraph(int n_, bool directed_ = false) { init(n_, directed_); } 25 | 26 | void init(int n_, bool directed_) { 27 | n = n_; 28 | directed = directed_; 29 | adj.assign(n, {}); 30 | } 31 | 32 | void addEdge(int u, int v, long long w) { 33 | adj[u].push_back({v, w}); 34 | if (!directed) adj[v].push_back({u, w}); 35 | } 36 | 37 | std::vector dijkstra(int source) const { 38 | const long long INF = std::numeric_limits::max() / 4; 39 | std::vector dist(n, INF); 40 | using State = std::pair; 41 | std::priority_queue, std::greater> pq; 42 | dist[source] = 0; 43 | pq.push({0, source}); 44 | while (!pq.empty()) { 45 | auto [d, u] = pq.top(); 46 | pq.pop(); 47 | if (d != dist[u]) continue; 48 | for (auto [v, w] : adj[u]) { 49 | if (dist[v] > d + w) { 50 | dist[v] = d + w; 51 | pq.push({dist[v], v}); 52 | } 53 | } 54 | } 55 | return dist; 56 | } 57 | 58 | std::vector zeroOneBfs(int source) const { 59 | const long long INF = std::numeric_limits::max() / 4; 60 | std::vector dist(n, INF); 61 | std::deque dq; 62 | dist[source] = 0; 63 | dq.push_back(source); 64 | while (!dq.empty()) { 65 | int u = dq.front(); 66 | dq.pop_front(); 67 | for (auto [v, w] : adj[u]) { 68 | if (dist[v] > dist[u] + w) { 69 | dist[v] = dist[u] + w; 70 | if (w == 0) dq.push_front(v); 71 | else dq.push_back(v); 72 | } 73 | } 74 | } 75 | return dist; 76 | } 77 | 78 | std::vector spfa(int source) const { 79 | const long long INF = std::numeric_limits::max() / 4; 80 | std::vector dist(n, INF); 81 | std::vector inQueue(n, 0); 82 | std::queue q; 83 | dist[source] = 0; 84 | q.push(source); 85 | inQueue[source] = 1; 86 | while (!q.empty()) { 87 | int u = q.front(); 88 | q.pop(); 89 | inQueue[u] = 0; 90 | for (auto [v, w] : adj[u]) { 91 | if (dist[v] > dist[u] + w) { 92 | dist[v] = dist[u] + w; 93 | if (!inQueue[v]) { 94 | inQueue[v] = 1; 95 | q.push(v); 96 | } 97 | } 98 | } 99 | } 100 | return dist; 101 | } 102 | }; 103 | 104 | } // namespace contest 105 | 106 | -------------------------------------------------------------------------------- /contest-cpp/random/DP/树上背包+状态机dp.md: -------------------------------------------------------------------------------- 1 | ![image-20251216181717422](image-20251216181717422.png) 2 | 3 | 这是一个**树形背包 DP**问题,结合了==「依赖关系」和「折扣机制」==,我们直接结合示例3来理解 4 | 5 | ![image-20251216181840826](image-20251216181840826.png) 6 | 7 | ------ 8 | 9 | ### **1. 问题建模(状态定义)** 10 | 11 | - **状态含义**:`f[j][k]` 表示在子树 `x` 中,使用预算 `j`,且父节点状态为 `k` 时的最大利润。 12 | 13 | - `k=0`:父节点**没买** → 当前节点 `x` **无折扣**(原价 `present[x]`) 14 | - `k=1`:父节点**买了** → 当前节点 `x` **有 5 折**(价格 `present[x]/2`) 15 | 16 | 💡 为什么需要 `k`?因为题目规定:**只有父节点买了,子节点才能享受折扣**。 17 | 18 | ------ 19 | 20 | ### **2. 状态转移** 21 | 22 | 分两步: 23 | 24 | #### **(1) 合并子树结果(背包合并)** 25 | 26 | - ==对每个子节点 `y`,将其所有可能的预算分配方案(`fy[jy][k]`)合并到当前节点 `x` 的 `sub_f[j][k]` 中==。 27 | - 类似「分组背包」:每个子树是一组物品,必须选一种预算分配方式。 28 | 29 | #### **(2) 决策当前节点(买 or 不买)** 30 | 31 | - **不买** `x`:利润 = 子树最大利润(此时子节点视为「父节点没买」→ `sub_f[j][0]`) 32 | - **买** `x`:利润 = 子树利润(子节点视为「父节点买了」→ `sub_f[j-cost][1]`) + `(future[x] - cost)` 33 | (`cost` 根据父节点状态 `k` 决定是原价还是半价) 34 | 35 | ------ 36 | 37 | ### **3. 初始化** 38 | 39 | - `sub_f` 初始全为 0(没有子节点时,利润为 0) 40 | - 递归从叶子节点向上计算(后序遍历) 41 | 42 | ------ 43 | 44 | ### **4. 遍历顺序** 45 | 46 | - **DFS 后序遍历**:先处理所有子节点,==再处理当前节点(依赖子问题结果==) 47 | - **背包逆序遍历**:`j` 从 `budget` 到 `0`,==避免重复选择同一子树== 48 | 49 | ------ 50 | 51 | ### **5. 最终答案** 52 | 53 | - `dfs(0)[budget][0]`:从根节点(0 号)出发,总预算 `budget`,且根节点**没有父节点**(视为 `k=0`,无折扣) 54 | 55 | ------ 56 | 57 | ### 🌰 举个栗子 58 | 59 | 假设: 60 | 61 | - 节点 0 是根,价格 10,未来价 15 62 | - 节点 1 是子节点,价格 8,未来价 12 63 | - 预算 = 12 64 | 65 | **决策过程**: 66 | 67 | 1. 如果**不买根**:只能用 12 元买子节点(无折扣,价格 8),利润 = 12-8=4 68 | 2. 如果**买根**:花 10 元,剩余 2 元 → 子节点享受 5 折(价格 4),但钱不够 → 利润 = 15-10=5 69 | **最终选方案 2(利润 5)** 70 | 71 | 这就是==DP 在自动比较所有可能性== 72 | 73 | ```cpp 74 | class Solution { 75 | public: 76 | int maxProfit(int n, vector& present, vector& future, vector>& hierarchy, int budget) 77 | { 78 | vector> g(n); 79 | for (auto& e : hierarchy) { 80 | g[e[0] - 1].push_back(e[1] - 1); 81 | }//建树-父指子 82 | 83 | auto dfs = [&](this auto&& dfs, int x) -> vector> 84 | { 85 | // 计算从 x 的所有儿子子树 y 中,能得到的最大利润之和(x 不买,x 买) 86 | vector> sub_f(budget + 1); 87 | for (int y : g[x]) { 88 | auto fy = dfs(y); 89 | for (int j = budget; j >= 0; j--) { 90 | // 枚举子树 y 的预算为 jy 91 | // 当作一个体积为 jy,价值为 fy[jy][k] 的物品 92 | for (int jy = 0; jy <= j; jy++) { 93 | for (int k = 0; k < 2; k++) { 94 | // k=0 表示 x 不买,k=1 表示 x 买 95 | sub_f[j][k] = max(sub_f[j][k], sub_f[j - jy][k] + fy[jy][k]); 96 | } 97 | } 98 | } 99 | } 100 | 101 | // 计算从子树 x 中,能得到的最大利润之和(x 父节点不买,x 父节点买) 102 | vector> f(budget + 1); 103 | for (int j = 0; j <= budget; j++) { 104 | for (int k = 0; k < 2; k++) { 105 | // k=0 表示 x 父节点不买,k=1 表示 x 父节点买 106 | int cost = present[x] / (k + 1); 107 | if (j >= cost) 108 | { 109 | // 不买 x,转移来源是 sub_f[j][0] 110 | // 买 x,转移来源为 sub_f[j-cost][1],因为对于子树来说,父节点一定买 111 | f[j][k] = max(sub_f[j][0], sub_f[j - cost][1] + future[x] - cost); 112 | } 113 | else 114 | { 115 | // 只能不买 x 116 | f[j][k] = sub_f[j][0]; 117 | } 118 | } 119 | } 120 | return f; 121 | }; 122 | return dfs(0)[budget][0]; 123 | } 124 | }; 125 | ``` 126 | 127 | -------------------------------------------------------------------------------- /数位dp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | //https://oi-wiki.org/dp/number/ 3 | 4 | #include 5 | using namespace std; 6 | constexpr int N = 15; 7 | using ll = long long; 8 | ll l, r, dp[N], mi[N]; 9 | ll ans1[N], ans2[N]; 10 | int a[N]; 11 | 12 | void solve(ll n, ll *ans) { 13 | ll tmp = n; 14 | int len = 0; 15 | while (n) a[++len] = n % 10, n /= 10; 16 | for (int i = len; i >= 1; --i) { 17 | for (int j = 0; j < 10; j++) ans[j] += dp[i - 1] * a[i]; 18 | for (int j = 0; j < a[i]; j++) ans[j] += mi[i - 1]; 19 | tmp -= mi[i - 1] * a[i], ans[a[i]] += tmp + 1; 20 | ans[0] -= mi[i - 1]; 21 | } 22 | } 23 | 24 | int main() { 25 | scanf("%lld%lld", &l, &r); 26 | mi[0] = 1ll; 27 | for (int i = 1; i <= 13; ++i) { 28 | dp[i] = dp[i - 1] * 10 + mi[i - 1]; 29 | mi[i] = 10ll * mi[i - 1]; 30 | } 31 | solve(r, ans1), solve(l - 1, ans2); 32 | for (int i = 0; i < 10; ++i) printf("%lld ", ans1[i] - ans2[i]); 33 | return 0; 34 | } 35 | 36 | */ 37 | 38 | 39 | class Solution { 40 | typedef long long ll; 41 | public: 42 | long long countNoZeroPairs(long long n) 43 | { 44 | if(n<=0) 45 | return 0; 46 | 47 | ll cnt[10][2][2]; 48 | ll m[2][2]; 49 | bool init=false; 50 | 51 | if(!init) 52 | { 53 | memset(cnt,0,sizeof(cnt)); 54 | 55 | for(int d=0;d<=9;d++) 56 | { 57 | for(int i=0;i<=1;i++) 58 | { 59 | for(int a=1;a<=9;a++) 60 | { 61 | for(int b=1;b<=9;b++) 62 | { 63 | int s=a+b+i; 64 | if(s%10==d) 65 | { 66 | if(s>=10) 67 | ++cnt[d][i][1]; 68 | else 69 | ++cnt[d][i][0]; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | m[0][0]=m[0][1]=m[1][0]=m[1][1]=0; 76 | for(int d=0;d<=9;d++) 77 | { 78 | m[0][0]+=cnt[d][0][0]; 79 | m[0][1]+=cnt[d][0][1]; 80 | m[1][0]+=cnt[d][1][0]; 81 | m[1][1]+=cnt[d][1][1]; 82 | } 83 | init=true; 84 | } 85 | 86 | auto t_cnt=[&](int c,int l)->ll 87 | { 88 | if(l==0) 89 | return (c==0); 90 | 91 | ll dp0=(c==0); 92 | ll dp1=(c==1); 93 | 94 | for(int i=0;i=1;i--) 114 | { 115 | int cur=a[i]; 116 | if(eq0) 117 | { 118 | int bg=(i==len)?1:0; 119 | for(int d=bg;d 3 | using namespace std; 4 | 5 | vector a, b, lazy; 6 | 7 | struct SqrtDecomposition { 8 | const int block_size, n; 9 | vector ls, rs; 10 | vector to_be_eval; 11 | vector>> first; 12 | 13 | explicit SqrtDecomposition(const int n_) 14 | : block_size((int)max(1.0, floor(sqrt((double)n_)))), 15 | n((n_ + block_size - 1) / block_size) { 16 | ls.resize(n); 17 | rs.resize(n); 18 | to_be_eval.assign(n, false); 19 | first.resize(n); 20 | for (int i = 0; i < n; ++i) { 21 | ls[i] = block_size * i; 22 | rs[i] = (i + 1 == n ? n_ : block_size * (i + 1)); 23 | first[i].reserve((rs[i] - ls[i]) * 2); 24 | } 25 | } 26 | 27 | void rebuild_block(int b) { 28 | vector> tmp; 29 | tmp.reserve(rs[b] - ls[b]); 30 | for (int i = ls[b]; i < rs[b]; ++i) tmp.emplace_back((int)a[i], i); 31 | sort(tmp.begin(), tmp.end(), [](const pair& x, const pair& y){ 32 | if (x.first != y.first) return x.first < y.first; 33 | return x.second < y.second; 34 | }); 35 | auto &mp = first[b]; 36 | mp.clear(); 37 | mp.reserve(tmp.size()); 38 | int last_key = INT_MIN; 39 | for (auto &p : tmp) { 40 | if (p.first != last_key) { 41 | last_key = p.first; 42 | mp.emplace_back(p.first, p.second); 43 | } 44 | } 45 | } 46 | 47 | void build_maps() { 48 | for (int b = 0; b < n; ++b) rebuild_block(b); 49 | } 50 | 51 | inline int get_real(int idx) const { 52 | int blk = idx / block_size; 53 | return (int)(a[idx] + lazy[blk]); 54 | } 55 | 56 | int find_leftmost_equal(int uptoIdx, int target) const { 57 | int br = uptoIdx / block_size; 58 | for (int b = 0; b < br; ++b) { 59 | int key = target - (int)lazy[b]; 60 | const auto &vec = first[b]; 61 | auto it = lower_bound(vec.begin(), vec.end(), key, 62 | [](const pair& p, int val){ return p.first < val; }); 63 | if (it != vec.end() && it->first == key) return it->second; 64 | } 65 | int key_lazy = (int)lazy[br]; 66 | for (int i = ls[br]; i <= uptoIdx; ++i) { 67 | if ((int)(a[i] + key_lazy) == target) return i; 68 | } 69 | return -1; 70 | } 71 | 72 | void range_add_and_fix(int l, int r, int val) { 73 | if (r <= l) return; 74 | const int b_l = l / block_size, b_r = (r - 1) / block_size; 75 | update(l, r, val); 76 | if (b_l == b_r) { 77 | rebuild_block(b_l); 78 | } else { 79 | if (l != ls[b_l]) rebuild_block(b_l); 80 | if (r != rs[b_r]) rebuild_block(b_r); 81 | } 82 | } 83 | 84 | template 85 | void partial_update(const int idx, const T val) { 86 | a[idx] += val; 87 | b[idx / block_size] += val; 88 | } 89 | 90 | template 91 | void total_update(const int idx, const T val) { 92 | lazy[idx] += val; 93 | to_be_eval[idx] = true; 94 | } 95 | 96 | template 97 | void update(const int l, const int r, const T val) { 98 | if (r <= l) return; 99 | const int b_l = l / block_size, b_r = (r - 1) / block_size; 100 | if (b_l < b_r) { 101 | if (l == ls[b_l]) { 102 | total_update(b_l, val); 103 | } else { 104 | for (int i = l; i < rs[b_l]; ++i) { 105 | partial_update(i, val); 106 | } 107 | } 108 | for (int i = b_l + 1; i < b_r; ++i) { 109 | total_update(i, val); 110 | } 111 | if (r == rs[b_r]) { 112 | total_update(b_r, val); 113 | } else { 114 | for (int i = ls[b_r]; i < r; ++i) { 115 | partial_update(i, val); 116 | } 117 | } 118 | } else { 119 | for (int i = l; i < r; ++i) { 120 | partial_update(i, val); 121 | } 122 | } 123 | } 124 | 125 | template 126 | void partial_query(const int idx, T* val) { 127 | const int block = idx / block_size; 128 | if (to_be_eval[block]) { 129 | for (int i = ls[block]; i < rs[block]; ++i) { 130 | partial_update(i, lazy[block]); 131 | } 132 | lazy[block] = 0; 133 | to_be_eval[block] = false; 134 | const_cast(this)->rebuild_block(block); 135 | } 136 | *val += a[idx]; 137 | } 138 | 139 | template 140 | void total_query(const int idx, T* val) { 141 | *val += b[idx] + lazy[idx] * (rs[idx] - ls[idx]); 142 | } 143 | 144 | template 145 | T query(const int l, const int r, const T id) { 146 | const int b_l = l / block_size, b_r = (r - 1) / block_size; 147 | T res = id; 148 | if (b_l < b_r) { 149 | if (l == ls[b_l]) { 150 | total_query(b_l, &res); 151 | } else { 152 | for (int i = l; i < rs[b_l]; ++i) { 153 | partial_query(i, &res); 154 | } 155 | } 156 | for (int i = b_l + 1; i < b_r; ++i) { 157 | total_query(i, &res); 158 | } 159 | if (r == rs[b_r]) { 160 | total_query(b_r, &res); 161 | } else { 162 | for (int i = ls[b_r]; i < r; ++i) { 163 | partial_query(i, &res); 164 | } 165 | } 166 | } else { 167 | for (int i = l; i < r; ++i) { 168 | partial_query(i, &res); 169 | } 170 | } 171 | return res; 172 | } 173 | }; 174 | 175 | class Solution { 176 | public: 177 | int longestBalanced(vector& nums) 178 | { 179 | int n=nums.size(); 180 | int N=n+1; 181 | SqrtDecomposition sd(N); 182 | a.assign(N,0); 183 | b.assign(sd.n,0); 184 | lazy.assign(sd.n,0); 185 | sd.build_maps(); 186 | int MAXV=100000; 187 | vector last(MAXV+1,-1); 188 | int ans=0; 189 | for(int r=0;r tips: 多刷题多体会~ 116 | 117 | #### 贪心算法 118 | 119 | - 与其说是贪心算法,不如说是贪心策略。 120 | 121 | 贪心策略:解决问题的策略( 局部最优 —> 全局最优)。 122 | 123 | - 把解决问题的过程分为若干步; 124 | - 解决每一步的时候,都选择当前看起来 “最优的” 解法; 125 | - “希望” 得到全局最优解。 126 | 127 | 遇到不会的贪心题,很正常,把心态放平。 128 | 129 | 1. 前期学习的时候,把重点放在贪心的策略上,把这个策略当成经验吸收。往后遇到相同类型的题目时可以用经验去解决这道问题。 130 | 2. 当知道贪心是正确的时候,要想到如何去证明。 131 | 132 | 可以从[\[贪心_1\] 介绍 | 柠檬水找零](https://lvynote.blog.csdn.net/article/details/147386582)等题目入手,体会贪心策略如何落地。 133 | 134 | [贪心算法] 135 | 136 | 1. [\[贪心_1\] 介绍 | 柠檬水找零](https://lvynote.blog.csdn.net/article/details/147386582) 137 | 2. [\[贪心_2\] \(含证明\)将数组和减半的最少操作次数 | 最大数](https://lvynote.blog.csdn.net/article/details/147398154) 138 | 3. [\[贪心_3\] 摆动序列 | 最长递增子序列](https://lvynote.blog.csdn.net/article/details/147422746) 139 | 4. [\[贪心_4\] 递增的三元子序列 | 最长连续递增序列 | 买卖股票的最佳时机 | & ||](https://lvynote.blog.csdn.net/article/details/147457750) 140 | 5. [\[贪心_5\] K 次取反后最大化的数组和 | 按身高排序](https://lvynote.blog.csdn.net/article/details/147475655) 141 | 6. [\[贪心_6\] 田忌赛马 | 最长回文串 | 增减字符串匹配 | 分发饼干](https://lvynote.blog.csdn.net/article/details/147500909) 142 | 7. [\[贪心_7\] 最优除法 | 跳跃游戏 II | 加油站](https://lvynote.blog.csdn.net/article/details/147518447) 143 | 8. [\[贪心_8\] 跳跃游戏 | 单调递增的数字 | 坏了的计算器](https://lvynote.blog.csdn.net/article/details/147528980) 144 | 9. [\[贪心_9\] 合并区间 | 无重叠区间 | 用最少数量的箭引爆气球](https://lvynote.blog.csdn.net/article/details/147539504) 145 | 146 | 按题型整理思路,记录解题步骤与常见坑点,形成可复用的知识框架。 147 | 148 | **笔记思路:** 可以参考这篇文章中t4数据流的中位数的梳理方法 [\[Lc14_priority_queue\] 最后一块石头重量 | 数据流中的第 K 大元素 | 前K个高频单词 | 数据流的中位数](https://lvynote.blog.csdn.net/article/details/146329248) 149 | 150 | > tips:记录的时候可以多思考,可能写一篇需要3-4个小时,但在之后刷题中可提效远不止3-4小时 151 | 152 | ### 好用的笔记工具 153 | 154 | - **draw.io** 155 | 电脑绘图工具:写笔记的时候思考如何清晰地让别人也可以看懂,可以帮助更好地理解学习。 156 | 示例图: 157 | ![draw.io 示例](./png/draw_io_test.png) 158 | - **xmind** 159 | 导图工具:以前几天每日一题中的区间贪心为例,可以借助导图清晰梳理题解(参考 [\[贪心_9\] 合并区间 | 无重叠区间 | 用最少数量的箭引爆气球](https://lvynote.blog.csdn.net/article/details/147539504))。 160 | - **Goodnotes** 161 | iPad 手绘工具:多种颜色画笔,可以清晰地对下面2个部分进行写写画画。 162 | 163 | 1. 写代码前:模拟代码,例如 [N 皇后 | 有效的数独](https://lvynote.blog.csdn.net/article/details/146631296)。 164 | 2. 写代码后:梳理代码,以 [正则表达式匹配](https://lvynote.blog.csdn.net/article/details/147333088) 为例,提炼关键状态转移并总结心得。 165 | - **LICEcup** 166 | 录屏 gif 动图,可用于演示 [递增的三元子序列 | 最长连续递增序列 | 买卖股票的最佳时机](https://lvynote.blog.csdn.net/article/details/147457750) 等题目的贪心过程。 167 | 168 | ### 2. 定期复习 169 | 170 | 复习是一件随时都可以做的事情,手机写代码不是很方便,可以想到的时候用来看代码,给代码做注释,就像是和代码二次对话一样 171 | 172 | ### 3. 刷题练习 173 | 174 | “工欲善其事必先利其器”,配置好环境可以让坐在电脑前刷题变的更加快乐:[VSCode配置LeetCode调试环境](https://lvynote.blog.csdn.net/article/details/145949237)(I love vscode) 175 | 176 | 题单:灵神的伟大无需多言(ㄒoㄒ)[分享|如何科学刷题?](https://leetcode.cn/discuss/post/3141566/ru-he-ke-xue-shua-ti-by-endlesscheng-q3yd/) 177 | 178 | ## 碎碎念 179 | 180 | 脑袋刷卡了可以换一件事情做,可以去 跑步/打球/ 游泳/爬山/组乐队/发呆...尽量从电子设备抽离,接触现实世界 181 | 182 | (偶尔steam也行,可以进入到另一个世界,如果长时间看屏幕注意滴眼药水保护视力 183 | 184 | 学会放松和学会刷题一样重要,健康的身心才可以更好的去做喜欢的事情 185 | 186 | --------- 187 | 188 | ### 使用指南 189 | 190 | - git clone 到本地,用 Typora 打开,可以直接对源.md文件进行自己的批注 191 | - 所有笔记 & 代码均为开源,支持自由获取/引用/改造,二创可以fork一份 192 | 193 | tips:如果没使用过 GitHub,复制仓库链接给 AI 并提问——“我该如何把这个仓库 clone 到本地使用”,碰到问题继续追问 AI,刚好实践学一下GitHub使用方法~ 194 | 195 | ----- 196 | 197 | ### to do list 198 | 待整理注释上传模板[png](./png/test.png),有好的模板大家也可以在仓库中分享开源出来 199 | 200 | templates部分包含简单模板,目前由[elainafan](https://github.com/elainafan)整理并持续更新。 201 | 202 | > 欢迎大家提issue/pr: 完善题单/补充想法/提出问题/学习交流...more٩( 'ω' )و 203 | 204 | ------ 205 | 206 | idea:周计划打卡 207 | 208 | - 后续star数多的话,计划把这个仓库的题单做成周计划 209 | - 制定一个学习&交流&共创的协议,可以在仓库下建分支,创建自己的打卡学习时间线,每天在分支下提交自己对所刷题目的注释代码/自我复盘/笔记/写写画画/碎碎念...过段时间自己回看会发现很有趣的 210 | 211 | 如果有机会我会尝试去创建/协助营造一个这样的学习空间,详见repo:[Openmind](https://github.com/lvy010/OpenMind)(in process),欢迎mark 212 | 213 | **期待一切有助于学习&进步的建议与共创~可以直接提issue/email me** 214 | 215 | ------ 216 | ## 算法之外(in process) 217 | 218 | 后续计划对系列文章进行总结整理,更新至repo:[X-Plore](https://github.com/lvy010/ThoughtMap)/从零开始的X学习之路 下 219 | 220 | 内容比较多,需要一些时间,可以先mark一下,有什么感兴趣的想先看到,可以在issue中对↓专栏文章进行选题投票(in process) 221 | 222 | ![x](./png/x.png) 223 | 224 | ### example content 225 | 226 | #### 从零开始的Linux学习之路 227 | 228 | ![Linux](./png/linux.png) 229 | 230 | #### 从零开始的LLM学习之路 231 | 232 | ![LLM](./png/llm.png) 233 | 234 | #### 从零开始的Mysql学习之路 235 | 236 | ![MySQL](./png/mysql.png) 237 | 238 | #### 从零开始的Redis学习之路 239 | 240 | ![Redis](./png/redis.png) 241 | 242 | #### 从零开始的C++源码学习之路 243 | 244 | ![C++](./png/cpp2.png) 245 | 246 | #### 从零开始的CUDA学习之路 247 | 248 | ![CUDA](./png/cuda.png) 249 | 250 | ... ...(more) 251 | 252 | 253 | ------- 254 | 附录: 255 | "那一天,人们回想起了这个仓库的起因,那篇被删掉的文章..." 256 | 仓库由来:[leetcode题单分享_第一版完整的原文截图](https://github.com/lvy010/Algo-Atlas/blob/main/Share_the_original_text.md)原文因为一些原因已经不存在了,这是一份备份。 257 | -------------------------------------------------------------------------------- /contest-cpp/random/传输协议编码.md: -------------------------------------------------------------------------------- 1 | ### leetcode 271 2 | 3 | 凡人#分割法 4 | 5 | ``` 6 | class Codec { 7 | public: 8 | string encode(vector& strs) 9 | { 10 | string res; 11 | for (int i = 0; i < strs.size(); i++) { 12 | // 使用 to_string 存储长度(文本格式) 13 | res += to_string(strs[i].size()); 14 | res += '#'; // 添加分隔符避免歧义 15 | res += strs[i]; 16 | } 17 | return res; 18 | } 19 | 20 | vector decode(string s) { 21 | vector res; 22 | int index = 0; 23 | while (index < s.size()) { 24 | // 找到 '#' 分隔符 25 | int pos = s.find('#', index); 26 | // 解析长度(文本转整数) 27 | int size = stoi(s.substr(index, pos - index)); 28 | index = pos + 1; 29 | // 提取字符串内容 30 | res.push_back(s.substr(index, size)); 31 | index += size; 32 | } 33 | return res; 34 | } 35 | }; 36 | ``` 37 | 38 | # 字符串序列化协议:基于长度前缀编码 39 | 40 | ## 引言 41 | 42 | 在分布式系统、网络通信和数据持久化场景中,我们经常需要将多个字符串序列化为单一的字节流进行传输或存储。一个设计良好的编码协议需要满足以下要求: 43 | 44 | 1. **无歧义性**:能够准确还原原始数据结构 45 | 2. **高效性**:编解码开销小,空间利用率高 46 | 3. **鲁棒性**:能够处理包含特殊字符的字符串 47 | 48 | 本文将分析一种基于**长度前缀编码(Length-Prefix Encoding)**的字符串序列化方案,并解读其C++实现的技术细节。 49 | 50 | ## 协议设计原理 51 | 52 | ### 核心思想 53 | 54 | 长度前缀编码的核心思想是:==在每个字符串前附加一个固定长度的整数字段==,表示后续字符串的字节长度。这种设计避免了使用分隔符可能带来的转义问题。 55 | 56 | ### 编码格式 57 | 58 | 对于字符串数组 `{"Hello", "World"}`,编码后的格式为: 59 | 60 | ``` 61 | [长度1][字符串1][长度2][字符串2]... 62 | ``` 63 | 64 | 具体到二进制层面(假设32位整数,小端序): 65 | 66 | ``` 67 | 原始数据: {"Hello", "World"} 68 | 编码结果: \x05\x00\x00\x00Hello\x05\x00\x00\x00World 69 | └─────┬─────┘└──┬──┘└─────┬─────┘└──┬──┘ 70 | 长度=5 内容 长度=5 内容 71 | ``` 72 | 73 | ### 协议优势 74 | 75 | 1. **无需转义**:字符串内容可以包含任意字节,包括空字符 `\0` 76 | 2. **定长头部**:==长度字段固定为 `sizeof(int)` 字节,解析效率高== 77 | 3. **流式处理**:支持顺序读取,内存占用可控 78 | 79 | ## 实现 80 | 81 | ### 代码 82 | 83 | ```cpp 84 | #include 85 | #include 86 | #include 87 | #include 88 | 89 | using namespace std; 90 | 91 | class Codec { 92 | public: 93 | /** 94 | * 编码函数:将字符串数组序列化为单一字符串 95 | * 96 | * @param strs 待编码的字符串向量 97 | * @return 编码后的字节流字符串 98 | * 99 | * 示例: 100 | * 输入: {"Hello", "World"} 101 | * 输出: "\x05\x00\x00\x00Hello\x05\x00\x00\x00World" 102 | */ 103 | string encode(vector& strs) { 104 | string res; 105 | 106 | // 预分配内存以提高性能(可选优化) 107 | size_t total_size = 0; 108 | for (const auto& str : strs) { 109 | total_size += sizeof(int) + str.size(); 110 | } 111 | res.reserve(total_size); 112 | 113 | // 逐个编码字符串 114 | for (const auto& str : strs) { 115 | int size = str.size(); 116 | 117 | // 1: 将整数的二进制表示直接写入字符串 118 | // string构造函数原型: string(const char* s, size_t n) 119 | // 功能: 从字符数组s复制前n个字节 120 | res += string(reinterpret_cast(&size), sizeof(size)); 121 | 122 | // 追加字符串内容 123 | res += str; 124 | } 125 | 126 | return res; 127 | } 128 | 129 | /** 130 | * 解码函数:从字节流还原字符串数组 131 | * 132 | * @param s 编码后的字节流字符串 133 | * @return 解码后的字符串向量 134 | * 135 | * 示例: 136 | * 输入: "\x05\x00\x00\x00Hello\x05\x00\x00\x00World" 137 | * 输出: {"Hello", "World"} 138 | */ 139 | vector decode(string s) { 140 | vector res; 141 | size_t index = 0; 142 | 143 | while (index < s.size()) { 144 | // 2: 从字节流重建整数 145 | int size = 0; 146 | // void* memcpy(void* dest, const void* src, size_t n) 147 | // s.data()返回指向内部字符数组的指针(C++11前==c_str()) 148 | memcpy(&size, s.data() + index, sizeof(size)); 149 | index += sizeof(size); 150 | 151 | // 提取指定长度的子字符串 152 | res.push_back(s.substr(index, size)); 153 | index += size; 154 | } 155 | 156 | return res; 157 | } 158 | }; 159 | ``` 160 | 161 | ### 解析 162 | 163 | #### 1. 整数到字节流的转换 164 | 165 | ```cpp 166 | int size = 5; 167 | string length_field(reinterpret_cast(&size), sizeof(size)); 168 | ``` 169 | 170 | **技术原理**: 171 | - `&size` 获取整数的内存地址 172 | - `reinterpret_cast` ==将整数指针重新解释为字符指针== 173 | - `string` 构造函数从字符数组复制 `sizeof(int)` 个字节 174 | 175 | **内存布局示例**(32位小端序系统): 176 | ``` 177 | 整数 5 的内存表示: 178 | 地址: 0x1000 0x1001 0x1002 0x1003 179 | 内容: 0x05 0x00 0x00 0x00 180 | └────────────┬────────────┘ 181 | 转换为字符串 182 | "\x05\x00\x00\x00" 183 | ``` 184 | 185 | #### 2. 字节流到整数的重建 186 | 187 | ```cpp 188 | int size = 0; 189 | memcpy(&size, s.data() + index, sizeof(size)); 190 | ``` 191 | 192 | **技术原理**: 193 | - `s.data()` 返回指向字符串内部缓冲区的指针 194 | - `memcpy` 从指定位置复制 `sizeof(int)` 个字节到 `size` 变量 195 | - 这是C语言风格的类型转换,效率高 196 | 197 | **API对比**: 198 | 199 | ```cpp 200 | // C++11之前 201 | const char* ptr = s.c_str(); // 返回C风格字符串 202 | 203 | // C++11之后 204 | const char* ptr = s.data(); // 功能相同,但语义更清晰 205 | ``` 206 | 207 | #### 3. 字符串构造函数的巧妙运用 208 | 209 | ```cpp 210 | // 构造函数原型 211 | string(const char* s, size_t n); 212 | ``` 213 | 214 | **使用场景**: 215 | ```cpp 216 | // 场景1: 从字节数组构造(可能包含\0) 217 | char buffer[] = {0x48, 0x00, 0x69}; // "H\0i" 218 | string str(buffer, 3); // 正确处理内嵌空字符 219 | cout << str.size(); // 输出: 3 220 | 221 | // 场景2: 提取子字符串 222 | string full = "Hello World"; 223 | string sub(full.data() + 6, 5); // "World" 224 | ``` 225 | 226 | ### 优化 227 | 228 | #### 1. 内存预分配 229 | 230 | ```cpp 231 | string encode(vector& strs) { 232 | // 计算总大小 233 | size_t total_size = 0; 234 | for (const auto& str : strs) { 235 | total_size += sizeof(int) + str.size(); 236 | } 237 | 238 | // 一次性分配内存,避免多次重新分配 239 | string res; 240 | res.reserve(total_size); 241 | 242 | // ... 编码逻辑 243 | } 244 | ``` 245 | 246 | **性能提升**:避免 `string` 多次扩容带来的内存拷贝开销。 247 | 248 | #### 2. 使用变长整数编码(VarInt) 249 | 250 | 对于大量短字符串的场景,固定4字节的长度字段存在浪费。可以采用变长编码: 251 | 252 | ```cpp 253 | // VarInt编码示例(类似Protocol Buffers) 254 | string encodeVarInt(int value) { 255 | string result; 256 | while (value >= 0x80) { 257 | result += static_cast((value & 0x7F) | 0x80); 258 | value >>= 7; 259 | } 260 | result += static_cast(value); 261 | return result; 262 | } 263 | ``` 264 | 265 | **优势**: 266 | - 小于128的长度只占1字节 267 | - 小于16384的长度只占2字节 268 | 269 | #### 3. 批量处理优化 270 | 271 | ```cpp 272 | // 使用ostringstream减少临时对象创建 273 | #include 274 | 275 | string encode(vector& strs) { 276 | ostringstream oss; 277 | for (const auto& str : strs) { 278 | int size = str.size(); 279 | oss.write(reinterpret_cast(&size), sizeof(size)); 280 | oss.write(str.data(), str.size()); 281 | } 282 | return oss.str(); 283 | } 284 | ``` 285 | 286 | ## 应用场景 287 | 288 | ### 1. 网络协议设计 289 | 290 | ```cpp 291 | // TCP消息帧格式 292 | struct MessageFrame { 293 | uint32_t length; // 消息长度(网络字节序) 294 | char data[]; // 消息内容 295 | }; 296 | 297 | // 使用本协议编码多个消息 298 | vector messages = {"MSG1", "MSG2", "MSG3"}; 299 | string encoded = codec.encode(messages); 300 | send(socket_fd, encoded.data(), encoded.size(), 0); 301 | ``` 302 | 303 | ### 2. 数据库序列化 304 | 305 | ```cpp 306 | // 将多列字符串数据序列化存储 307 | class RowSerializer { 308 | Codec codec; 309 | public: 310 | string serialize(const vector& row) { 311 | return codec.encode(const_cast&>(row)); 312 | } 313 | 314 | vector deserialize(const string& data) { 315 | return codec.decode(data); 316 | } 317 | }; 318 | ``` 319 | 320 | ### 3. 缓存系统 321 | 322 | ```cpp 323 | // Redis-like缓存的批量操作 324 | class CacheClient { 325 | Codec codec; 326 | public: 327 | void mset(const vector& keys, const vector& values) { 328 | vector combined; 329 | combined.insert(combined.end(), keys.begin(), keys.end()); 330 | combined.insert(combined.end(), values.begin(), values.end()); 331 | 332 | string encoded = codec.encode(combined); 333 | // 发送到缓存服务器 334 | } 335 | }; 336 | ``` 337 | 338 | ## 边界情况与错误处理 339 | 340 | ### 1. 空字符串处理 341 | 342 | ```cpp 343 | vector test = {"", "Hello", ""}; 344 | string encoded = codec.encode(test); 345 | // 编码结果: "\x00\x00\x00\x00\x05\x00\x00\x00Hello\x00\x00\x00\x00" 346 | ``` 347 | 348 | ### 2. 大字符串处理 349 | 350 | ```cpp 351 | // 对于超大字符串,考虑使用64位长度字段 352 | class Codec64 { 353 | string encode(vector& strs) { 354 | string res; 355 | for (const auto& str : strs) { 356 | uint64_t size = str.size(); // 使用64位 357 | res += string(reinterpret_cast(&size), sizeof(size)); 358 | res += str; 359 | } 360 | return res; 361 | } 362 | }; 363 | ``` 364 | 365 | ### 3. 数据校验 366 | 367 | ```cpp 368 | vector decode(string s) { 369 | vector res; 370 | size_t index = 0; 371 | 372 | while (index < s.size()) { 373 | // 边界检查 374 | if (index + sizeof(int) > s.size()) { 375 | throw runtime_error("Corrupted data: incomplete length field"); 376 | } 377 | 378 | int size = 0; 379 | memcpy(&size, s.data() + index, sizeof(size)); 380 | index += sizeof(int); 381 | 382 | // 长度校验 383 | if (size < 0 || index + size > s.size()) { 384 | throw runtime_error("Corrupted data: invalid length"); 385 | } 386 | 387 | res.push_back(s.substr(index, size)); 388 | index += size; 389 | } 390 | 391 | return res; 392 | } 393 | ``` 394 | 395 | ## 跨平台兼容性考虑 396 | 397 | ### 字节序问题 398 | 399 | 不同CPU架构的字节序可能不同(大端序 vs 小端序)。对于跨平台传输,需要统一字节序: 400 | 401 | ```cpp 402 | #include // Linux/Unix 403 | // #include // Windows 404 | 405 | string encode(vector& strs) { 406 | string res; 407 | for (const auto& str : strs) { 408 | uint32_t size = str.size(); 409 | uint32_t network_size = htonl(size); // 转换为网络字节序(大端序) 410 | res += string(reinterpret_cast(&network_size), sizeof(network_size)); 411 | res += str; 412 | } 413 | return res; 414 | } 415 | 416 | vector decode(string s) { 417 | vector res; 418 | size_t index = 0; 419 | 420 | while (index < s.size()) { 421 | uint32_t network_size = 0; 422 | memcpy(&network_size, s.data() + index, sizeof(network_size)); 423 | uint32_t size = ntohl(network_size); // 转换为主机字节序 424 | index += sizeof(network_size); 425 | 426 | res.push_back(s.substr(index, size)); 427 | index += size; 428 | } 429 | 430 | return res; 431 | } 432 | ``` 433 | 434 | ## 性能基准测试 435 | 436 | ```cpp 437 | #include 438 | 439 | void benchmark() { 440 | // 生成测试数据 441 | vector test_data; 442 | for (int i = 0; i < 10000; i++) { 443 | test_data.push_back(string(100, 'A' + (i % 26))); 444 | } 445 | 446 | Codec codec; 447 | 448 | // 编码性能测试 449 | auto start = chrono::high_resolution_clock::now(); 450 | string encoded = codec.encode(test_data); 451 | auto end = chrono::high_resolution_clock::now(); 452 | auto encode_time = chrono::duration_cast(end - start); 453 | 454 | // 解码性能测试 455 | start = chrono::high_resolution_clock::now(); 456 | vector decoded = codec.decode(encoded); 457 | end = chrono::high_resolution_clock::now(); 458 | auto decode_time = chrono::duration_cast(end - start); 459 | 460 | cout << "编码时间: " << encode_time.count() << " μs\n"; 461 | cout << "解码时间: " << decode_time.count() << " μs\n"; 462 | cout << "原始大小: " << test_data.size() * 100 << " bytes\n"; 463 | cout << "编码大小: " << encoded.size() << " bytes\n"; 464 | cout << "空间开销: " << (encoded.size() - test_data.size() * 100) << " bytes\n"; 465 | } 466 | ``` 467 | 468 | **典型测试结果**(10000个100字节字符串): 469 | ``` 470 | 编码时间: 1523 μs 471 | 解码时间: 1876 μs 472 | 原始大小: 1000000 bytes 473 | 编码大小: 1040000 bytes 474 | 空间开销: 40000 bytes (4%) 475 | ``` 476 | 477 | ## 总结 478 | 479 | 本文介绍的长度前缀编码方案是一种经典且高效的字符串序列化协议,其核心优势在于: 480 | 481 | 1. **简洁性**:实现仅需不到50行代码 482 | 2. **高效性**:编解码时间复杂度为线性,空间开销固定 483 | 3. **鲁棒性**:支持任意字节内容,无需转义处理 484 | 4. **实用性**:广泛应用于网络协议、数据库、缓存等系统 485 | 486 | 在实际应用中,可以根据具体场景进行优化: 487 | - 对于短字符串场景,使用变长整数编码减少空间开销 488 | - 对于跨平台传输,统一使用网络字节序 489 | - 对于性能敏感场景,使用内存预分配和批量处理 490 | 491 | 这种设计思想也体现了计算机系统设计的核心原则:**==在简洁性和效率之间寻找最佳平衡点==**。 492 | 493 | --- 494 | 495 | **参考资源**: 496 | - [LeetCode 271: Encode and Decode Strings](https://leetcode.cn/problems/encode-and-decode-strings/) 497 | - [Protocol Buffers Encoding](https://developers.google.com/protocol-buffers/docs/encoding) 498 | - [C++ String Reference](https://en.cppreference.com/w/cpp/string/basic_string) 499 | 500 | --------------------------------------------------------------------------------