[{"body":"","link":"https://www.cosefinch.com/categories/","section":"categories","tags":null,"title":"Categories"},{"body":"题目信息 字段 内容 题号 P1616 难度 橙题（普及-） 知识点 动态规划、背包问题 题目描述 LiYuxiang 是个天资聪颖的孩子，他的梦想是成为世界上最伟大的医师。为此，他想拜附近最有威望的医师为师。医师为了判断他的天赋，给他出了一个难题：\n医师把他带到到处都是草药的山洞里，对他说：「孩子，这个山洞里有一些不同的草药，采每一株都需要一些时间，每一株也有它自身的价值。我会给你一段时间，在这段时间里，你可以采到一些草药。如果你是一个聪明的孩子，你应该可以让采到的草药的总价值最大。」\n此题与 P1048 采药的不同之处在于：每种草药可以无限制地疯狂采摘。\n输入格式 第一行有两个整数 T（总时间）和 M（草药种类数） 接下来 M 行，每行两个整数 a_i, b_i，分别表示采摘第 i 种草药所需的时间 a_i 和它价值 b_i 输出格式 一个整数，表示在规定时间内可以采到的草药的最大总价值。\n数据范围 变量 范围 T 0 ≤ T ≤ 10^7 M 0 ≤ M ≤ 10^4 a_i 0 \u0026lt; a_i ≤ 10^7 b_i 0 \u0026lt; b_i ≤ 10^7 样例 输入\n70 3 71 100 69 1 1 2 输出\n140 解释：时间 T=70，有 3 种草药。第一种需要 71 时间（采不了），第二种价值 1 但耗时 69，第三种耗时 1 价值 2，可以采 70 次，总价值 140。\n解题思路 核心思想 本题与 P1048「采药」的唯一区别在于：每种草药可以采摘无限次。这使得它从一个 0-1 背包问题变成了一个完全背包（Complete Knapsack）问题。\n完全背包与 0-1 背包的关键区别 问题类型 每种物品 内层循环方向 状态转移 0-1 背包 只能选一次 逆序 j 从 T → a_i 防止重复选取 完全背包 可选无限次 正序 j 从 a_i → T 允许重复选取 为什么正序遍历就能实现「无限次选取」？\n以 dp[j] = max(dp[j], dp[j-a_i] + b_i) 为例：\n当 j 正序递增时，dp[j-a_i] 在此轮中已经计算过（因为 j-a_i \u0026lt; j，且正序下 j-a_i 比 j 先被更新） 这意味着 dp[j-a_i] 可能已经包含了一份 a_i，再用它更新 dp[j]，就相当于「再选一次 a_i」 以此类推，同一轮中可以多次选 a_i → 实现无限次选取 ✅ 状态定义 dp[j]：用恰好 j 时间采药，能获得的最大总价值。\n状态转移方程 dp[j] = max(dp[j], dp[j - a_i] + b_i), j ≥ a_i\n完整代码 C++ #include \u0026lt;cstdio\u0026gt; #include \u0026lt;algorithm\u0026gt; typedef long long ll; const int kMaxT = 10000005; ll dp[kMaxT]; int main() { int t = 0, m = 0; scanf(\u0026#34;%d%d\u0026#34;, \u0026amp;t, \u0026amp;m); for (int i = 1; i \u0026lt;= m; ++i) { int a = 0, b = 0; scanf(\u0026#34;%d%d\u0026#34;, \u0026amp;a, \u0026amp;b); // 完全背包：正序遍历，允许重复选取 for (int j = a; j \u0026lt;= t; ++j) { dp[j] = std::max(dp[j], dp[j - a] + b); } } printf(\u0026#34;%lld\\n\u0026#34;, dp[t]); return 0; } 原提交代码说明：你提供的代码中 dp[j] = dp[j]; 是一句无效语句（将自身赋值给自身），实际上 j \u0026lt; a[i] 时什么也不做，可以直接从 j = a[i] 开始循环，简化代码。\n关键代码讲解 为什么内层循环从 j = a[i] 开始？ 当 j \u0026lt; a_i 时，当前草药 i 连一次都采不了，无需更新 dp[j]，初始值（即上一轮的结果）已经是最优的。直接从 j = a_i 开始既正确又高效。\n原代码的问题分析 你提交的代码：\nfor (int j = 0; j \u0026lt;= T; j++) { if (j \u0026lt; a[t]) { dp[j] = dp[j]; // ❌ 无效语句，等于什么都不做 } else { dp[j] = max(dp[j], dp[j - a[t]] + b[t]); } } 问题：\ndp[j] = dp[j]; 是无效操作，编译器可能会优化掉，应直接删除（或让循环从 j = a[t] 开始） 多余的头文件：#include \u0026lt;queue/stack/set/map/vector/cmath\u0026gt; 均未使用 const int maxn = 1e7 + 5 定义过大，int a[maxn] 占用约 40MB，在栈上分配可能导致段错误，应改为局部变量或用 vector 修正后的写法（更简洁）：\nfor (int j = a[i]; j \u0026lt;= t; ++j) { dp[j] = std::max(dp[j], dp[j - a[i]] + b[i]); } 复杂度分析 方法 时间复杂度 空间复杂度 一维完全背包 O(M * T) O(T) M ≤ 10^4，T ≤ 10^7，最坏情况下 O(M * T) 约 10^11 会超时 但实际上洛谷数据梯度设计合理，且 C++ 优化后可以通过 如仍超时，可进一步用「单调队列优化完全背包」，将复杂度降为 O(M * T) 的常数级优化 易错点 内层循环方向搞反：写成逆序 for (j = T; j \u0026gt;= a[i]; j--) 就变成 0-1 背包，只能通过每种草药一次，与题意不符！ dp 数组类型要用 long long：M=10^4，b_i=10^4 时，最大价值可达 10^4 × 10^7 = 10^11，超出 int 范围。 数组大小要开够：T 最大 10^7，dp 数组需要 10^7+5 大小的 long long，约 80MB，注意内存限制（洛谷此题为 256MB，刚好够用）。 不要用 cin/cout 读入大量数据：(M ≤ 10^4 不算大，但配合 T 较大时 cin 较慢，建议用 scanf。 其他语言解法 Python import sys def main() -\u0026gt; None: data = sys.stdin.buffer.read().split() t = int(data[0]) m = int(data[1]) dp = [0] * (t + 1) idx = 2 for _ in range(m): a = int(data[idx]); b = int(data[idx + 1]); idx += 2 # 完全背包：正序遍历 for j in range(a, t + 1): if dp[j - a] + b \u0026gt; dp[j]: dp[j] = dp[j - a] + b print(dp[t]) if __name__ == \u0026#34;__main__\u0026#34;: main() ⚠️ Python 情况下 T 最大 10^7 会导致内存和时间超时，建议仅用于小数据理解算法。\nJava import java.io.BufferedInputStream; import java.io.InputStream; import java.io.DataInputStream; public class Main { private static int nextInt(InputStream in) throws Exception { int c, val = 0; while ((c = in.read()) \u0026lt;= 32) {} do { val = val * 10 + (c - \u0026#39;0\u0026#39;); } while ((c = in.read()) \u0026gt; 32); return val; } public static void main(String[] args) throws Exception { InputStream in = new BufferedInputStream(System.in); int t = nextInt(in); int m = nextInt(in); long[] dp = new long[t + 1]; for (int i = 0; i \u0026lt; m; i++) { int a = nextInt(in); int b = nextInt(in); for (int j = a; j \u0026lt;= t; j++) { if (dp[j - a] + b \u0026gt; dp[j]) { dp[j] = dp[j - a] + b; } } } System.out.println(dp[t]); } } Go package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { in := bufio.NewReader(os.Stdin) var t, m int fmt.Fscan(in, \u0026amp;t, \u0026amp;m) dp := make([]int64, t+1) for i := 0; i \u0026lt; m; i++ { var a, b int fmt.Fscan(in, \u0026amp;a, \u0026amp;b) for j := a; j \u0026lt;= t; j++ { v := dp[j-a] + int64(b) if v \u0026gt; dp[j] { dp[j] = v } } } fmt.Println(dp[t]) } 注：Go 语言的 int 在 64 位系统上是 64 位，但显式使用 int64 更安全。\nJavaScript (Node.js) \u0026#39;use strict\u0026#39;; const fs = require(\u0026#39;fs\u0026#39;); const data = fs.readFileSync(0, \u0026#39;utf-8\u0026#39;).trim().split(/\\s+/).map(Number); const t = data[0]; const m = data[1]; const dp = new Array(t + 1).fill(0); let idx = 2; for (let i = 0; i \u0026lt; m; i++) { const a = data[idx++]; const b = data[idx++]; for (let j = a; j \u0026lt;= t; j++) { const v = dp[j - a] + b; if (v \u0026gt; dp[j]) dp[j] = v; } } console.log(dp[t]); ⚠️ Node.js 在 T=10^7 时同样会超时/超内存，仅用于小数据验证算法正确性。\n","link":"https://www.cosefinch.com/posts/p1616-crazy-herb-picking/","section":"posts","tags":["动态规划","背包问题"],"title":"P1616 疯狂的采药"},{"body":"","link":"https://www.cosefinch.com/posts/","section":"posts","tags":null,"title":"Posts"},{"body":"","link":"https://www.cosefinch.com/tags/","section":"tags","tags":null,"title":"Tags"},{"body":"","link":"https://www.cosefinch.com/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/","section":"tags","tags":null,"title":"动态规划"},{"body":"","link":"https://www.cosefinch.com/","section":"","tags":null,"title":"敲代码的朱雀"},{"body":"","link":"https://www.cosefinch.com/categories/%E6%99%AE%E5%8F%8A-/","section":"categories","tags":null,"title":"普及-"},{"body":"","link":"https://www.cosefinch.com/tags/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/","section":"tags","tags":null,"title":"背包问题"},{"body":"题目信息 字段 内容 题号 P1025 难度 橙题（普及+） 知识点 递归、动态规划、整数划分 题目描述 将整数 n 分成 k 份，且每份不能为空，任意两个方案不相同（不考虑顺序）。\n例如：n = 7, k = 3，下面三种分法被认为是相同的：\n(1,1,5) (1,5,1) (5,1,1)\n问有多少种不同的分法。\n输入格式 一行两个正整数 n, k（6 \u0026lt; n ≤ 200, 2 ≤ k ≤ 6）。\n输出格式 一个整数，即不同的分法数。\n样例 输入\n7 3 输出\n4 解释：7 分成 3 份共有 4 种分法：1,1,5、1,2,4、1,3,3、2,2,3。\n解题思路 核心思想 本题要求将 n 个相同的苹果放到 k 个不同的盘子中，每个盘子至少一个苹果，且不计顺序。这等价于求整数 n 的 k 部分无序拆分数。\n核心递推思路基于对最大划分部分的讨论：\n定义 f(m, n) 表示将整数 m 划分为不超过 n 个正整数之和的方案数（即每份最多为 n），则有：\nf(m, n) = f(m, n-1) + f(m-n, n)\n含义是：\nf(m, n-1)：所有划分中不出现 n 的方案，等价于最大部分 ≤ n-1 f(m-n, n)：所有划分中至少出现一个 n 的方案，减去一个 n 后变成将 m-n 划分为最大部分 ≤ n 的问题 与原问题的联系：将 n 分成恰好 k 份 → 先给每份放 1 个苹果（保证非空），剩下 n-k 个苹果随意分配到 k 份中（允许某些份不再增加）。这就是为什么代码中调用的是 f(apple - plate, plate)。\n递归边界 条件 返回值 含义 m = 0 1 恰好分完，是一种合法方案 n = 1 1 所有划分中最大部分不超过 1，只有全 1 这一种方案 m \u0026lt; n f(m, m) 要划分的数比上限还小，上限缩小到 m 完整代码 C++ #include \u0026lt;iostream\u0026gt; using namespace std; int apple, plate; // f(m, n): 将整数 m 划分为最大部分不超过 n 的方案数 int f(int m, int n) { if (m == 0 || n == 1) return 1; // 边界：恰好分完 或 只能放 1 if (m \u0026lt; n) return f(m, m); // 上限大于剩余数，缩小上限 return f(m - n, n) + f(m, n - 1); // 转移：包含 n + 不包含 n } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin \u0026gt;\u0026gt; apple \u0026gt;\u0026gt; plate; // 先给每份放 1 个苹果，再对剩余 apple-plate 个苹果做无限制划分 cout \u0026lt;\u0026lt; f(apple - plate, plate) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 为什么是 f(apple - plate, plate) 而不是 f(apple, plate)？ 题目要求每份至少一个，等价于\u0026quot;先给 k 个盘子各放 1 个苹果，再将剩下的 n-k 个苹果任意分配\u0026quot;。所以实际要划分的数量是 n - k，而非 n。\n理解递归转移 以 n = 7, k = 3 为例，实际调用 f(4, 3)：\nf(4, 3) = f(4, 2) + f(1, 3) = [f(4,1) + f(2,2)] + [f(1,1)] = [1 + (f(0,2) + f(2,1))] + [1] = [1 + (1 + 1)] + 1 = 4 对应 4 种分法：1+1+5, 1+2+4, 1+3+3, 2+2+3（加上每份固定的 1，即还原为原问题的分法）。\n复杂度分析 方法 时间复杂度 空间复杂度 递归（无记忆化） O(2^n) O(n)（递归栈） 本题数据范围 n ≤ 200，纯递归会有大量重复计算。可以通过记忆化搜索将时间优化到 O(n * k)，也可以转化为二维动态规划。\n记忆化优化版本 #include \u0026lt;iostream\u0026gt; #include \u0026lt;cstring\u0026gt; using namespace std; int apple, plate; int memo[205][205]; int f(int m, int n) { if (m == 0 || n == 1) return 1; if (m \u0026lt; n) return f(m, m); if (memo[m][n] != -1) return memo[m][n]; return memo[m][n] = f(m - n, n) + f(m, n - 1); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin \u0026gt;\u0026gt; apple \u0026gt;\u0026gt; plate; memset(memo, -1, sizeof(memo)); cout \u0026lt;\u0026lt; f(apple - plate, plate) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 记忆化后时间复杂度优化为 O(n * k)，空间复杂度 O(n * k)。\n易错点 不要直接调用 f(apple, plate)：这样计算的是\u0026quot;将 n 分成最大不超过 k 份\u0026quot;，允许某些份为 0，与题意不符。 递归深度：本题 n ≤ 200，递归深度不会超过 200，不会栈溢出。但如果数据范围更大，建议改用递推（动态规划）。 数据类型：当 n = 200, k = 6 时答案较大但不会超过 int 范围，无需使用 long long。 其他语言解法 Python import sys sys.setrecursionlimit(10000) def f(m: int, n: int) -\u0026gt; int: \u0026#34;\u0026#34;\u0026#34;将 m 划分为最大部分不超过 n 的方案数\u0026#34;\u0026#34;\u0026#34; if m == 0 or n == 1: return 1 if m \u0026lt; n: return f(m, m) return f(m - n, n) + f(m, n - 1) if __name__ == \u0026#34;__main__\u0026#34;: apple, plate = map(int, sys.stdin.readline().split()) print(f(apple - plate, plate)) Java import java.io.BufferedInputStream; import java.io.InputStream; import java.util.Arrays; public class Main { static int[][] memo = new int[205][205]; static int f(int m, int n) { if (m == 0 || n == 1) return 1; if (m \u0026lt; n) return f(m, m); if (memo[m][n] != -1) return memo[m][n]; return memo[m][n] = f(m - n, n) + f(m, n - 1); } public static void main(String[] args) { InputStream in = new BufferedInputStream(System.in); int apple = nextInt(in), plate = nextInt(in); for (int[] row : memo) Arrays.fill(row, -1); System.out.println(f(apple - plate, plate)); } static int nextInt(InputStream in) { int c, val = 0; try { while ((c = in.read()) \u0026lt;= 32) {} do { val = val * 10 + (c - \u0026#39;0\u0026#39;); } while ((c = in.read()) \u0026gt; 32); } catch (Exception e) {} return val; } } Go package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) var memo [205][205]int func f(m, n int) int { if m == 0 || n == 1 { return 1 } if m \u0026lt; n { return f(m, m) } if memo[m][n] != -1 { return memo[m][n] } memo[m][n] = f(m-n, n) + f(m, n-1) return memo[m][n] } func main() { reader := bufio.NewReader(os.Stdin) var apple, plate int fmt.Fscan(reader, \u0026amp;apple, \u0026amp;plate) for i := range memo { for j := range memo[i] { memo[i][j] = -1 } } fmt.Println(f(apple-plate, plate)) } JavaScript \u0026#39;use strict\u0026#39;; const memo = Array.from({ length: 205 }, () =\u0026gt; new Int32Array(205).fill(-1)); function f(m, n) { if (m === 0 || n === 1) return 1; if (m \u0026lt; n) return f(m, m); if (memo[m][n] !== -1) return memo[m][n]; return memo[m][n] = f(m - n, n) + f(m, n - 1); } const [apple, plate] = require(\u0026#39;fs\u0026#39;) .readFileSync(\u0026#39;/dev/stdin\u0026#39;, \u0026#39;utf8\u0026#39;) .trim() .split(\u0026#39; \u0026#39;) .map(Number); console.log(f(apple - plate, plate)); ","link":"https://www.cosefinch.com/posts/p1025-integer-partition/","section":"posts","tags":["动态规划"],"title":"P1025 数的划分"},{"body":"","link":"https://www.cosefinch.com/categories/%E6%99%AE%E5%8F%8A+/","section":"categories","tags":null,"title":"普及+"},{"body":"题目信息 字段 内容 题号 P1009 难度 橙题（普及-） 知识点 高精度、循环 题目描述 用高精度计算出：\nS = 1! + 2! + 3! + ... + n! (n ≤ 50)\n其中 ! 表示阶乘，定义为 n! = n × (n-1) × ... × 1，特别地 0! = 1。\n输入格式：一个整数 n。\n输出格式：一个整数，表示阶乘之和 S。\n解题思路 核心思想 由于 n ≤ 50，50! 已经是一个非常大的数（约 65 位十进制数字），超出了任何内置整数类型的范围，因此必须使用高精度来计算。\n整体思路分两步：\n递推计算阶乘：利用 (i-1)! 计算 i!，避免重复计算 高精度加法：将每个阶乘的结果累加到总和中 高精度实现思路 我们用低位优先的方式存储大整数：用一个数组（或 vector）从下标 0 开始存放个位、十位、百位……\n例如数字 12345 存储为 [5, 4, 3, 2, 1]。\n需要两个基本操作：\n高精度 × 低精度：计算 a *= b（b 是普通整数） 高精度 + 高精度：计算 a += b 完整代码 C++ #include \u0026lt;iostream\u0026gt; #include \u0026lt;vector\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; // 高精度乘法：digits（低位优先）乘以一个普通整数 multiplier void multiply(vector\u0026lt;int\u0026gt;\u0026amp; digits, int multiplier) { int carry = 0; for (int i = 0; i \u0026lt; digits.size(); i++) { carry += digits[i] * multiplier; digits[i] = carry % 10; carry /= 10; } while (carry) { digits.push_back(carry % 10); carry /= 10; } } // 高精度加法：a += b（a、b 均为低位优先） void add(vector\u0026lt;int\u0026gt;\u0026amp; a, const vector\u0026lt;int\u0026gt;\u0026amp; b) { int carry = 0; int max_len = max(a.size(), b.size()); a.resize(max_len, 0); for (int i = 0; i \u0026lt; max_len; i++) { int digit_b = i \u0026lt; b.size() ? b[i] : 0; carry += a[i] + digit_b; a[i] = carry % 10; carry /= 10; } if (carry) { a.push_back(carry); } } // 打印高精度数（低位优先存储，打印时需逆序） void print_big(const vector\u0026lt;int\u0026gt;\u0026amp; digits) { for (int i = digits.size() - 1; i \u0026gt;= 0; i--) { cout \u0026lt;\u0026lt; digits[i]; } cout \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin \u0026gt;\u0026gt; n; vector\u0026lt;int\u0026gt; factorial = {1}; // 0! = 1 vector\u0026lt;int\u0026gt; sum = {0}; // 总和，初始为 0 for (int i = 1; i \u0026lt;= n; i++) { multiply(factorial, i); // factorial 现在等于 i! add(sum, factorial); // sum += i! } print_big(sum); return 0; } Python \u0026#34;\u0026#34;\u0026#34;P1009 阶乘之和 - Python 实现（高精度）\u0026#34;\u0026#34;\u0026#34; from typing import List def solve() -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;读取 n，计算 1! + 2! + ... + n! 并输出\u0026#34;\u0026#34;\u0026#34; n: int = int(input().strip()) factorial: int = 1 # 当前阶乘值 total: int = 0 # 阶乘之和 for i in range(1, n + 1): factorial *= i # i! = (i-1)! × i total += factorial print(total) if __name__ == \u0026#34;__main__\u0026#34;: solve() Java import java.io.IOException; import java.math.BigInteger; public class Main { // 洛谷强制要求类名为 Main，禁止使用 Scanner，用 BufferedInputStream private static final byte[] BUFFER = new byte[1 \u0026lt;\u0026lt; 16]; private static int ptr = 0; private static int len = 0; private static int read() throws IOException { if (ptr \u0026gt;= len) { len = System.in.read(BUFFER); ptr = 0; if (len \u0026lt;= 0) return -1; } return BUFFER[ptr++]; } private static int readInt() throws IOException { int c, val = 0; do { c = read(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); boolean neg = (c == \u0026#39;-\u0026#39;); if (neg) c = read(); while (c \u0026gt; \u0026#39; \u0026#39;) { val = val * 10 + (c - \u0026#39;0\u0026#39;); c = read(); } return neg ? -val : val; } public static void main(String[] args) throws IOException { int n = readInt(); BigInteger factorial = BigInteger.ONE; // 0! = 1 BigInteger sum = BigInteger.ZERO; for (int i = 1; i \u0026lt;= n; i++) { factorial = factorial.multiply(BigInteger.valueOf(i)); // i! sum = sum.add(factorial); // sum += i! } System.out.println(sum); } } Go package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;math/big\u0026#34; \u0026#34;os\u0026#34; ) func main() { in := bufio.NewReader(os.Stdin) var n int fmt.Fscan(in, \u0026amp;n) factorial := big.NewInt(1) // 0! = 1 sum := big.NewInt(0) // 总和 for i := 1; i \u0026lt;= n; i++ { factorial.Mul(factorial, big.NewInt(int64(i))) // i! sum.Add(sum, factorial) // sum += i! } fmt.Println(sum) } JavaScript \u0026#39;use strict\u0026#39;; /** * 计算 1! + 2! + ... + n!（使用 BigInt 高精度） */ function main() { const input = require(\u0026#39;fs\u0026#39;).readFileSync(0, \u0026#39;utf8\u0026#39;).trim().split(/\\s+/); const n = Number(input[0]); let factorial = 1n; // 当前阶乘值（BigInt） let sum = 0n; // 阶乘之和（BigInt） for (let i = 1; i \u0026lt;= n; i++) { factorial *= BigInt(i); // i! sum += factorial; // sum += i! } console.log(sum.toString()); } main(); 关键代码讲解 1. 低位优先存储 // 数字 12345 存储为 [5, 4, 3, 2, 1] vector\u0026lt;int\u0026gt; digits = {5, 4, 3, 2, 1}; 这样设计的好处是：进位和扩展位数只需要 push_back，不需要移动整个数组。\n2. 高精度乘法（大数 × 小数） void multiply(vector\u0026lt;int\u0026gt;\u0026amp; digits, int multiplier) { int carry = 0; for (int i = 0; i \u0026lt; digits.size(); i++) { carry += digits[i] * multiplier; digits[i] = carry % 10; // 当前位 carry /= 10; // 进位 } while (carry) { // 还有进位，扩展位数 digits.push_back(carry % 10); carry /= 10; } } 3. Python 的天然优势 Python 内置支持任意精度整数，直接写 factorial *= i 即可，不需要手动实现高精度，代码非常简洁。\n复杂度分析 方法 时间复杂度 空间复杂度 高精度模拟 O(n × L) O(L) 其中 L 是结果的位数，L ≈ log10(n!) ≈ n log10 n。\n易错点 进位处理不彻底：乘法中 while (carry) 不能省略，否则高位会丢失 加法位数不对齐：两个加数的位数可能不同，需要先 resize 再计算 打印顺序：存储是低位优先，打印时必须逆序输出 n 的范围：n=50 时结果约有 65 位，任何内置类型都存不下，必须用高精度 ","link":"https://www.cosefinch.com/posts/p1009-sum-of-factorial/","section":"posts","tags":["高精度"],"title":"P1009 阶乘之和"},{"body":"","link":"https://www.cosefinch.com/tags/%E9%AB%98%E7%B2%BE%E5%BA%A6/","section":"tags","tags":null,"title":"高精度"},{"body":"题目信息 字段 内容 题号 P1048 难度 橙题（普及-） 知识点 动态规划、背包问题 题目描述 辰辰是个天资聪颖的孩子，他的梦想是成为世界上最伟大的医师。为此，他想拜附近最有威望的医师为师。医师为了判断他的资质，给他出了一个难题：\n医师把他带到一个到处都是草药的山洞里对他说：\u0026ldquo;孩子，这个山洞里有一些不同的草药，采每一株药需要一些时间，每一株药也有它自身的价值。我会给你一段时间，在这段时间里，你可以采到一些草药。如果你是一个聪明的孩子，你应该可以让采到的草药的总价值最大。\u0026rdquo;\n现在已知山洞里各株草药采药需要花费的时间、该株草药的价值，以及你总共可以用于采药的时间。求在规定时间内可以采到的草药的最大总价值。\n输入格式：第一行两个整数 T（总时间）和 M（草药数量），接下来 M 行每行两个整数，分别表示采该株药需要花费的时间和该株药的价值。\n输出格式：一个整数，表示在规定时间内可以采到的草药的最大总价值。\n解题思路 核心思想 这是0-1 背包问题的经典模板：\n每种草药要么采（选），要么不采（不选），不能重复采 目标：在总时间 T 内，使总价值最大 DP 状态定义 dp[j]：用恰好 j 分钟采药，能获得的最大价值。\n状态转移方程 对于第 i 株药（耗时 w[i]，价值 v[i]）：\ndp[j] = max(dp[j], dp[j - w[i]] + v[i]) 含义：不采第 i 株药，价值是 dp[j]；采第 i 株药，价值是 dp[j - w[i]] + v[i]，取较大者。\n为什么内层循环要倒序？ for (int j = T; j \u0026gt;= w[i]; j--) 倒序遍历是为了保证每个物品只能被选一次：\n如果正序遍历，在更新 dp[j] 时，dp[j - w[i]] 可能已经被本轮更新过了（即第 i 个物品被选了两次），变成了完全背包 倒序遍历时，dp[j - w[i]] 一定还是上一轮（i-1）的值，保证了每个物品最多选一次 完整代码 C++ #include \u0026lt;iostream\u0026gt; #include \u0026lt;algorithm\u0026gt; using namespace std; const int kMaxT = 10001; const int kMaxM = 101; int w[kMaxM]; // 每株药需要的时间 int v[kMaxM]; // 每株药的价值 int dp[kMaxT]; // dp[j]：用 j 分钟的最大价值 int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t, m; cin \u0026gt;\u0026gt; t \u0026gt;\u0026gt; m; for (int i = 1; i \u0026lt;= m; i++) { cin \u0026gt;\u0026gt; w[i] \u0026gt;\u0026gt; v[i]; } // 0-1 背包：外层遍历物品，内层倒序遍历容量 for (int i = 1; i \u0026lt;= m; i++) { for (int j = t; j \u0026gt;= w[i]; j--) { dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } cout \u0026lt;\u0026lt; dp[t] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 1. 数组大小设定 const int kMaxT = 10001; // T ≤ 1000，保守取 10001 const int kMaxM = 101; // M ≤ 100，保守取 101 洛谷 P1048 的数据范围是 T ≤ 1000，M ≤ 100，数组开大一点防止边界溢出。\n2. 倒序遍历的核心 for (int j = t; j \u0026gt;= w[i]; j--) dp[j] = max(dp[j], dp[j - w[i]] + v[i]); j \u0026gt;= w[i]：剩余时间不够采这株药，直接跳过 倒序保证了 dp[j - w[i]] 是上一轮的状态（未选第 i 株药时的最优解） 3. 初始化 dp 数组全局定义，默认全为 0，表示一开始任何时间都不采药，价值为 0。这是 0-1 背包的标准初始化方式。\n复杂度分析 方法 时间复杂度 空间复杂度 一维 DP（空间优化） O(T × M) O(T) T ≤ 1000，M ≤ 100，最大 10^5 次运算，洛谷限时完全够用 空间从 O(T × M) 优化到了 O(T)，节省了大量内存 易错点 内层循环必须倒序：正序会变成完全背包（物品可重复选），这是最容易错的地方 j 的下界是 w[i] 不是 0：剩余时间比采这株药的时间还少时，这株药根本采不了 数组大小：dp 的大小要至少 T + 1，用 10001 比较安全 ios::sync_with_stdio(false)：洛谷输入量较大，必须关闭同步加速 其他语言解法 Python \u0026#34;\u0026#34;\u0026#34;P1048 采药 - Python 实现（0-1 背包）\u0026#34;\u0026#34;\u0026#34; from typing import List def solve() -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;读取输入，计算在 T 时间内可采药的最大总价值\u0026#34;\u0026#34;\u0026#34; t, m = map(int, input().split()) w: List[int] = [0] * (m + 1) v: List[int] = [0] * (m + 1) for i in range(1, m + 1): w[i], v[i] = map(int, input().split()) # dp[j]：用 j 分钟的最大价值 dp: List[int] = [0] * (t + 1) for i in range(1, m + 1): # 倒序遍历，保证每个物品只选一次 for j in range(t, w[i] - 1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) print(dp[t]) if __name__ == \u0026#34;__main__\u0026#34;: solve() Java import java.io.IOException; public class Main { // 洛谷强制要求类名为 Main，禁止使用 Scanner，用 BufferedInputStream private static final byte[] BUFFER = new byte[1 \u0026lt;\u0026lt; 16]; private static int ptr = 0; private static int len = 0; private static int read() throws IOException { if (ptr \u0026gt;= len) { len = System.in.read(BUFFER); ptr = 0; if (len \u0026lt;= 0) return -1; } return BUFFER[ptr++]; } private static int readInt() throws IOException { int c, val = 0; do { c = read(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); boolean neg = (c == \u0026#39;-\u0026#39;); if (neg) c = read(); while (c \u0026gt; \u0026#39; \u0026#39;) { val = val * 10 + (c - \u0026#39;0\u0026#39;); c = read(); } return neg ? -val : val; } public static void main(String[] args) throws IOException { int t = readInt(); int m = readInt(); int[] w = new int[m + 1]; int[] v = new int[m + 1]; for (int i = 1; i \u0026lt;= m; i++) { w[i] = readInt(); v[i] = readInt(); } int[] dp = new int[t + 1]; for (int i = 1; i \u0026lt;= m; i++) { for (int j = t; j \u0026gt;= w[i]; j--) { dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); } } System.out.println(dp[t]); } } Go package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) func main() { in := bufio.NewReader(os.Stdin) var t, m int fmt.Fscan(in, \u0026amp;t, \u0026amp;m) w := make([]int, m+1) v := make([]int, m+1) for i := 1; i \u0026lt;= m; i++ { fmt.Fscan(in, \u0026amp;w[i], \u0026amp;v[i]) } dp := make([]int, t+1) for i := 1; i \u0026lt;= m; i++ { for j := t; j \u0026gt;= w[i]; j-- { if dp[j] \u0026lt; dp[j-w[i]]+v[i] { dp[j] = dp[j-w[i]] + v[i] } } } fmt.Println(dp[t]) } JavaScript \u0026#39;use strict\u0026#39;; /** * 0-1 背包：在 T 时间内采药，求最大总价值 */ function main() { const input = require(\u0026#39;fs\u0026#39;).readFileSync(0, \u0026#39;utf8\u0026#39;).trim().split(/\\s+/).map(Number); let idx = 0; const t = input[idx++]; const m = input[idx++]; const w = [0]; const v = [0]; for (let i = 0; i \u0026lt; m; i++) { w.push(input[idx++]); v.push(input[idx++]); } /** @type {number[]} */ const dp = new Array(t + 1).fill(0); for (let i = 1; i \u0026lt;= m; i++) { for (let j = t; j \u0026gt;= w[i]; j--) { dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]); } } console.log(dp[t]); } main(); ","link":"https://www.cosefinch.com/posts/p1048-herbal-medicine/","section":"posts","tags":["动态规划","背包问题"],"title":"P1048 采药"},{"body":"题目信息 字段 内容 题号 P1002 难度 橙题（普及-） 知识点 动态规划、棋盘型DP 题目描述 有一个 n × m 的棋盘（中国象棋棋盘），卒从左上角 (0, 0) 出发，要到达右下角 (n, m)。 棋盘上有一匹马，位置在 (x, y)。象棋马的走法是「日」字，马所在的格子以及马下一步能跳到的 8 个位置都会被控制，卒不能经过这些格子。\n求从 (0, 0) 到 (n, m) 共有多少条不同的路径（只能向右或向下走）。\n输入格式：四个整数 n, m, x, y，空格分隔。\n输出格式：一个整数，表示路径数量。\n解题思路 核心思想 分两步走：\n标记禁区：马在 (x, y) 位置，会控制周围的 8 个格子。先把这些格子标记为不可达。 方向 DP：棋盘上只能向右或向下走，所以每个格子的路径数 = 从上方来的 + 从左方来的，这就是经典的棋盘路径 DP。 马的控制范围 与中国象棋的马走法一致，从 (x, y) 出发共 8 个目标位置：\n(x-2) (y-1) (y+1) (x+2) (y-1) (y+1) (x-1) (y-2) (y+2) (x+1) (y-2) (y+2) 超出棋盘边界的格子直接忽略。\n为什么用动态规划？ 因为只能向右或向下走，棋盘形成了一个 DAG（有向无环图）。从左到右、从上到下按顺序遍历，每个格子只会被访问一次，时间复杂度是 O(n×m)。\n完整代码 C++ #include \u0026lt;iostream\u0026gt; using namespace std; // 马的八个跳跃方向（象棋马走日字） const int kDx[9] = {0, 1, 2, 2, 1, -1, -2, -2, -1}; const int kDy[9] = {0, 2, 1, -1, -2, -2, -1, 1, 2}; long long dp[30][30]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); long long n, m, x, y; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m \u0026gt;\u0026gt; x \u0026gt;\u0026gt; y; // dp 数组大小为 (n+1) × (m+1)，索引范围 0~n 和 0~m // 标记禁区：马所在格及其控制格 for (int i = 0; i \u0026lt;= 8; i++) { int tx = x + kDx[i]; int ty = y + kDy[i]; if (tx \u0026gt;= 0 \u0026amp;\u0026amp; tx \u0026lt;= n \u0026amp;\u0026amp; ty \u0026gt;= 0 \u0026amp;\u0026amp; ty \u0026lt;= m) { dp[tx][ty] = -1; // -1 表示不可达 } } // 方向 DP：只能从上方或左方到达 for (int i = 0; i \u0026lt;= n; i++) { for (int j = 0; j \u0026lt;= m; j++) { if (dp[i][j] != -1) { long long from_top = 0; long long from_left = 0; if (i - 1 \u0026gt;= 0 \u0026amp;\u0026amp; dp[i - 1][j] != -1) { from_top = dp[i - 1][j]; } if (j - 1 \u0026gt;= 0 \u0026amp;\u0026amp; dp[i][j - 1] != -1) { from_left = dp[i][j - 1]; } dp[i][j] = from_top + from_left; if (i == 0 \u0026amp;\u0026amp; j == 0) { dp[i][j] = 1; // 起点有一种方式 } } } } cout \u0026lt;\u0026lt; dp[n][m] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 1. 禁区标记 const int kDx[9] = {0, 1, 2, 2, 1, -1, -2, -2, -1}; const int kDy[9] = {0, 2, 1, -1, -2, -2, -1, 1, 2}; dx[0] 和 dy[0] 都是 0，表示马所在的自身格子也要标记为禁区。其余 8 个是标准象棋马的跳跃偏移量。\nif (tx \u0026gt;= 0 \u0026amp;\u0026amp; tx \u0026lt;= n \u0026amp;\u0026amp; ty \u0026gt;= 0 \u0026amp;\u0026amp; ty \u0026lt;= m) { dp[tx][ty] = -1; // 在棋盘范围内才标记 } 边界检查必不可少，否则数组会越界。\n2. 方向 DP 转移 dp[i][j] = (i \u0026gt; 0 ? dp[i-1][j] : 0) + (j \u0026gt; 0 ? dp[i][j-1] : 0); 由于棋盘只能从左和从上两个方向到达，状态转移方程就是上方的路径数加上左方的路径数。\n3. 起点的特殊处理 if (i == 0 \u0026amp;\u0026amp; j == 0) dp[i][j] = 1; 左上角 (0, 0) 没有上方也没有左方，特殊处理为 1（自己算一种走法）。\n复杂度分析 方法 时间复杂度 空间复杂度 棋盘 DP O(n × m) O(n × m) n, m ≤ 25，复杂度完全足够。\n易错点 索引范围：棋盘大小是 (n+1) × (m+1)，循环上界要用 \u0026lt;= n 和 \u0026lt;= m 马的控制范围包括自身：dx[0]=0, dy[0]=0 不要漏掉，否则起点或终点恰好是马的位置时会算错 负数索引：边界检查 \u0026lt;= n 和 \u0026lt;= m 不能写成 \u0026lt; n 和 \u0026lt; m long long：路径数可能很大，答案会超过 int 范围，必须用 long long 其他语言解法 Python \u0026#34;\u0026#34;\u0026#34;P1002 过河卒 - Python 实现\u0026#34;\u0026#34;\u0026#34; from typing import List def solve() -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;读取输入，计算从 (0,0) 到 (n,m) 的路径数\u0026#34;\u0026#34;\u0026#34; n, m, x, y = map(int, input().split()) # dp[i][j]: 到达 (i, j) 的路径数，-1 表示不可达 dp: List[List[int]] = [[0] * (m + 1) for _ in range(n + 1)] # 马的八个跳跃方向 k_dx = [0, 1, 2, 2, 1, -1, -2, -2, -1] k_dy = [0, 2, 1, -1, -2, -2, -1, 1, 2] # 标记禁区 for i in range(9): tx = x + k_dx[i] ty = y + k_dy[i] if 0 \u0026lt;= tx \u0026lt;= n and 0 \u0026lt;= ty \u0026lt;= m: dp[tx][ty] = -1 # 方向 DP for i in range(n + 1): for j in range(m + 1): if dp[i][j] != -1: if i == 0 and j == 0: dp[i][j] = 1 else: from_top = dp[i - 1][j] if i \u0026gt; 0 and dp[i - 1][j] != -1 else 0 from_left = dp[i][j - 1] if j \u0026gt; 0 and dp[i][j - 1] != -1 else 0 dp[i][j] = from_top + from_left print(dp[n][m]) if __name__ == \u0026#34;__main__\u0026#34;: solve() Java import java.io.IOException; public class Main { // 洛谷强制要求类名为 Main，禁止使用 Scanner，用 BufferedInputStream private static final byte[] buffer = new byte[1 \u0026lt;\u0026lt; 16]; private static int ptr = 0; private static int len = 0; private static int read() throws IOException { if (ptr \u0026gt;= len) { len = System.in.read(buffer); ptr = 0; if (len \u0026lt;= 0) return -1; } return buffer[ptr++]; } private static final int[] kDx = {0, 1, 2, 2, 1, -1, -2, -2, -1}; private static final int[] kDy = {0, 2, 1, -1, -2, -2, -1, 1, 2}; public static void main(String[] args) throws IOException { long n = readInt(); long m = readInt(); long x = readInt(); long y = readInt(); long[][] dp = new long[(int) (n + 1)][(int) (m + 1)]; // 标记禁区 for (int i = 0; i \u0026lt;= 8; i++) { int tx = (int) (x + kDx[i]); int ty = (int) (y + kDy[i]); if (tx \u0026gt;= 0 \u0026amp;\u0026amp; tx \u0026lt;= n \u0026amp;\u0026amp; ty \u0026gt;= 0 \u0026amp;\u0026amp; ty \u0026lt;= m) { dp[tx][ty] = -1; } } // 方向 DP for (int i = 0; i \u0026lt;= n; i++) { for (int j = 0; j \u0026lt;= m; j++) { if (dp[i][j] != -1) { if (i == 0 \u0026amp;\u0026amp; j == 0) { dp[i][j] = 1; } else { long fromTop = (i \u0026gt; 0 \u0026amp;\u0026amp; dp[i - 1][j] != -1) ? dp[i - 1][j] : 0; long fromLeft = (j \u0026gt; 0 \u0026amp;\u0026amp; dp[i][j - 1] != -1) ? dp[i][j - 1] : 0; dp[i][j] = fromTop + fromLeft; } } } } System.out.println(dp[(int) n][(int) m]); } private static long readInt() throws IOException { int c; do { c = read(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); boolean negative = false; if (c == \u0026#39;-\u0026#39;) { negative = true; c = read(); } long val = 0; while (c \u0026gt; \u0026#39; \u0026#39;) { val = val * 10 + (c - \u0026#39;0\u0026#39;); c = read(); } return negative ? -val : val; } } Go package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; ) var ( kDx = [9]int{0, 1, 2, 2, 1, -1, -2, -2, -1} kDy = [9]int{0, 2, 1, -1, -2, -2, -1, 1, 2} ) func main() { in := bufio.NewReader(os.Stdin) var n, m, x, y int fmt.Fscan(in, \u0026amp;n, \u0026amp;m, \u0026amp;x, \u0026amp;y) dp := make([][]int, n+1) for i := 0; i \u0026lt;= n; i++ { dp[i] = make([]int, m+1) } // 标记禁区 for i := 0; i \u0026lt;= 8; i++ { tx := x + kDx[i] ty := y + kDy[i] if tx \u0026gt;= 0 \u0026amp;\u0026amp; tx \u0026lt;= n \u0026amp;\u0026amp; ty \u0026gt;= 0 \u0026amp;\u0026amp; ty \u0026lt;= m { dp[tx][ty] = -1 } } // 方向 DP for i := 0; i \u0026lt;= n; i++ { for j := 0; j \u0026lt;= m; j++ { if dp[i][j] != -1 { if i == 0 \u0026amp;\u0026amp; j == 0 { dp[i][j] = 1 } else { fromTop := 0 fromLeft := 0 if i \u0026gt; 0 \u0026amp;\u0026amp; dp[i-1][j] != -1 { fromTop = dp[i-1][j] } if j \u0026gt; 0 \u0026amp;\u0026amp; dp[i][j-1] != -1 { fromLeft = dp[i][j-1] } dp[i][j] = fromTop + fromLeft } } } } fmt.Println(dp[n][m]) } JavaScript \u0026#39;use strict\u0026#39;; /** * 马的八个跳跃方向（象棋马走日字） * @type {number[]} */ const kDx = [0, 1, 2, 2, 1, -1, -2, -2, -1]; const kDy = [0, 2, 1, -1, -2, -2, -1, 1, 2]; /** * 主函数：从 (0,0) 到 (n,m) 的路径数，排除马的控制点 */ function main() { const input = require(\u0026#39;fs\u0026#39;).readFileSync(\u0026#39;/dev/stdin\u0026#39;, \u0026#39;utf8\u0026#39;).trim().split(/\\s+/); const [n, m, x, y] = input.map(Number); /** @type {number[][]} */ const dp = Array.from({ length: n + 1 }, () =\u0026gt; Array(m + 1).fill(0)); // 标记禁区：马所在格及其控制格 for (let i = 0; i \u0026lt;= 8; i++) { const tx = x + kDx[i]; const ty = y + kDy[i]; if (tx \u0026gt;= 0 \u0026amp;\u0026amp; tx \u0026lt;= n \u0026amp;\u0026amp; ty \u0026gt;= 0 \u0026amp;\u0026amp; ty \u0026lt;= m) { dp[tx][ty] = -1; } } // 方向 DP：只能从上方或左方到达 for (let i = 0; i \u0026lt;= n; i++) { for (let j = 0; j \u0026lt;= m; j++) { if (dp[i][j] !== -1) { if (i === 0 \u0026amp;\u0026amp; j === 0) { dp[i][j] = 1; } else { const fromTop = (i \u0026gt; 0 \u0026amp;\u0026amp; dp[i - 1][j] !== -1) ? dp[i - 1][j] : 0; const fromLeft = (j \u0026gt; 0 \u0026amp;\u0026amp; dp[i][j - 1] !== -1) ? dp[i][j - 1] : 0; dp[i][j] = fromTop + fromLeft; } } } } console.log(dp[n][m]); } main(); ","link":"https://www.cosefinch.com/posts/p1002-crossing-river-pawn/","section":"posts","tags":["动态规划"],"title":"P1002 过河卒"},{"body":"题目信息 字段 内容 题号 P1008 难度 橙题（普及-） 知识点 枚举 题目描述 将 1, 2, \u0026hellip;, 9 共 9 个数分成三组，分别组成三个三位数，且使这三个三位数的比例正好是 1:2:3。\n要求：\n三个三位数必须全部由 1~9 组成，每个数字恰好用一次，不能重复也不能遗漏 找出所有满足条件的三个三位数，按格式输出 解题思路 核心思想 使用枚举 + 去重检查的方法：\n最小的那个三位数最小是 123，最大不超过 329（因为 329×3=987，再大就超出三位数范围了） 枚举最小数 a，则另外两个数分别为 b = 2a，c = 3a 将 a、b、c 的每一位数字拆出来，共 9 个数字 检查这 9 个数字是否恰好是 1~9 各出现一次（用数组计数即可） 为什么这样枚举？ 如果比例是 1:2:3，那么设最小的数为 a，另外两个数就唯一确定了 只需枚举 a，避免了复杂的全排列，时间复杂度从 O(9!) 降到了 O(329) 完整代码 C++ #include \u0026lt;iostream\u0026gt; #include \u0026lt;array\u0026gt; using namespace std; // 检查三个数 a, b, c 的各位数字是否恰好包含 1~9 各一次 bool Check(int a, int b, int c) { array\u0026lt;int, 10\u0026gt; count{0}; // 拆出 a 的每一位 count[a / 100]++; count[a / 10 % 10]++; count[a % 10]++; // 拆出 b 的每一位 count[b / 100]++; count[b / 10 % 10]++; count[b % 10]++; // 拆出 c 的每一位 count[c / 100]++; count[c / 10 % 10]++; count[c % 10]++; // 检查 1~9 是否各出现一次，且不包含 0 for (int i = 1; i \u0026lt;= 9; i++) { if (count[i] != 1) { return false; } } return true; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); // 枚举最小的数 a，范围是 123 ~ 329 for (int a = 123; a \u0026lt;= 329; a++) { int b = a * 2; int c = a * 3; if (Check(a, b, c)) { cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; c \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } return 0; } 关键代码讲解 数字拆解 count[a / 100]++; // 百位数 count[a / 10 % 10]++; // 十位数 count[a % 10]++; // 个位数 用整除和取余操作拆出每一位数字，比转成字符串更高效。\n范围为什么是 123~329？ 最小三位数的百位最小是 1，所以 a ≥ 123（三个数字不重复的最小三位数） c = 3a 必须是三位数，所以 3a ≤ 987，即 a ≤ 329 复杂度分析 方法 时间复杂度 空间复杂度 枚举法 O(200) O(1) 实际上只需要循环约 200 次，每次检查 9 个数字，是非常高效的暴力解法。\n易错点 数字不能包含 0：1~9 各用一次，不能出现数字 0，计数检查时要注意 输出顺序：按第一个数从小到大输出，题目要求的格式是 a b c（空格分隔） 边界范围：a 的上界是 329 不是 333，因为 333×3=999 但数字有重复 其他语言解法 Python \u0026#34;\u0026#34;\u0026#34;P1008 三连击 - Python 实现\u0026#34;\u0026#34;\u0026#34; from typing import List def check(a: int, b: int, c: int) -\u0026gt; bool: \u0026#34;\u0026#34;\u0026#34;检查三个数的各位数字是否恰好包含 1~9 各一次\u0026#34;\u0026#34;\u0026#34; count = [0] * 10 for num in (a, b, c): count[num // 100] += 1 count[num // 10 % 10] += 1 count[num % 10] += 1 for i in range(1, 10): if count[i] != 1: return False return True def main() -\u0026gt; None: \u0026#34;\u0026#34;\u0026#34;枚举最小的数 a（范围 123~329），检查 2a 和 3a 是否满足条件\u0026#34;\u0026#34;\u0026#34; results: List[str] = [] for a in range(123, 330): b = a * 2 c = a * 3 if check(a, b, c): results.append(f\u0026#34;{a} {b} {c}\u0026#34;) print(\u0026#34;\\n\u0026#34;.join(results)) if __name__ == \u0026#34;__main__\u0026#34;: main() Java import java.io.IOException; public class Main { // 洛谷强制要求类名为 Main，且禁止使用 Scanner（MLE），使用 BufferedInputStream private static final byte[] buffer = new byte[1 \u0026lt;\u0026lt; 16]; private static int ptr = 0; private static int len = 0; private static int read() throws IOException { if (ptr \u0026gt;= len) { len = System.in.read(buffer); ptr = 0; if (len \u0026lt;= 0) return -1; } return buffer[ptr++]; } private static boolean check(int a, int b, int c) { int[] count = new int[10]; // 拆出 a 的每一位 count[a / 100]++; count[a / 10 % 10]++; count[a % 10]++; // 拆出 b 的每一位 count[b / 100]++; count[b / 10 % 10]++; count[b % 10]++; // 拆出 c 的每一位 count[c / 100]++; count[c / 10 % 10]++; count[c % 10]++; // 检查 1~9 是否各出现一次 for (int i = 1; i \u0026lt;= 9; i++) { if (count[i] != 1) { return false; } } return true; } public static void main(String[] args) throws IOException { StringBuilder sb = new StringBuilder(); // 枚举最小的数 a，范围是 123 ~ 329 for (int a = 123; a \u0026lt;= 329; a++) { int b = a * 2; int c = a * 3; if (check(a, b, c)) { sb.append(a).append(\u0026#34; \u0026#34;).append(b).append(\u0026#34; \u0026#34;).append(c).append(\u0026#39;\\n\u0026#39;); } } System.out.print(sb.toString()); } } Go package main import ( \u0026#34;fmt\u0026#34; \u0026#34;strings\u0026#34; ) // check 检查三个数的各位数字是否恰好包含 1~9 各一次 func check(a, b, c int) bool { count := [10]int{0} // 拆出 a 的每一位 count[a/100]++ count[a/10%10]++ count[a%10]++ // 拆出 b 的每一位 count[b/100]++ count[b/10%10]++ count[b%10]++ // 拆出 c 的每一位 count[c/100]++ count[c/10%10]++ count[c%10]++ // 检查 1~9 是否各出现一次 for i := 1; i \u0026lt;= 9; i++ { if count[i] != 1 { return false } } return true } func main() { var results []string // 枚举最小的数 a，范围是 123 ~ 329 for a := 123; a \u0026lt;= 329; a++ { b := a * 2 c := a * 3 if check(a, b, c) { results = append(results, fmt.Sprintf(\u0026#34;%d %d %d\u0026#34;, a, b, c)) } } fmt.Print(strings.Join(results, \u0026#34;\\n\u0026#34;)) } JavaScript \u0026#39;use strict\u0026#39;; /** * 检查三个数的各位数字是否恰好包含 1~9 各一次 * @param {number} a * @param {number} b * @param {number} c * @returns {boolean} */ function check(a, b, c) { /** @type {number[]} */ const count = Array(10).fill(0); // 拆出 a 的每一位 count[Math.floor(a / 100)]++; count[Math.floor(a / 10) % 10]++; count[a % 10]++; // 拆出 b 的每一位 count[Math.floor(b / 100)]++; count[Math.floor(b / 10) % 10]++; count[b % 10]++; // 拆出 c 的每一位 count[Math.floor(c / 100)]++; count[Math.floor(c / 10) % 10]++; count[c % 10]++; // 检查 1~9 是否各出现一次 for (let i = 1; i \u0026lt;= 9; i++) { if (count[i] !== 1) { return false; } } return true; } /** * 主函数：枚举最小的数 a（范围 123~329），检查 2a 和 3a 是否满足条件 */ function main() { const results = []; for (let a = 123; a \u0026lt;= 329; a++) { const b = a * 2; const c = a * 3; if (check(a, b, c)) { results.push(`${a} ${b} ${c}`); } } console.log(results.join(\u0026#39;\\n\u0026#39;)); } main(); 正确答案 本题共有 4 组解：\n192 384 576 219 438 657 273 546 819 327 654 981 ","link":"https://www.cosefinch.com/posts/p1008-triple/","section":"posts","tags":["枚举"],"title":"P1008 三连击"},{"body":"","link":"https://www.cosefinch.com/tags/%E6%9E%9A%E4%B8%BE/","section":"tags","tags":null,"title":"枚举"},{"body":"题目信息 字段 内容 题号 P1006 难度 绿题（普及+） 知识点 动态规划、方格 DP 题目描述 小渊和小轩是好朋友，他们在一起总有谈不完的话题。\n一次素质拓展活动中，班上同学安排坐成一个 m 行 n 列的矩阵，小渊坐在矩阵的左上角，坐标 (1,1)；小轩坐在矩阵的右下角，坐标 (m,n)。\n从小渊到小轩的纸条可以向下或向右传递；从小轩到小渊的纸条可以向上或向左传递（本质上仍是两条从左上到右下的不相交路径）。\n他们希望对他们的友好指数进行量化评估。评估方法为：找出两条从小渊到小轩的传递路径（可以重合，但同格指数只算一次），使得路径上同学的友好指数之和最大。\n输入格式：\n第一行两个整数 m 和 n，表示矩阵有 m 行、n 列（1 ≤ m, n ≤ 50）。\n接下来 m 行，每行 n 个整数，表示每位同学的友好指数（0 ≤ 指数 ≤ 10000）。\n输出格式：\n一个整数，表示两条路径上友好指数之和的最大值。\n样例：\n输入\n3 3 0 3 9 2 8 5 5 7 0 输出\n34 样例解释：\n两条最优路径分别为：\n路径一（从小渊到小轩）：(1,1) → (1,2) → (1,3) → (2,3) → (3,3)，指数和 = 0 + 3 + 9 + 5 + 0 = 17 路径二（从小轩到小渊，同样从左上到右下处理）：(1,1) → (2,1) → (2,2) → (3,2) → (3,3)，指数和 = 0 + 2 + 8 + 7 + 0 = 17 总和 = 34。注意坐标 (1,1) 和 (3,3) 被两条路径共享，指数只计算一次。\n解题思路 核心思想：四维动态规划 这是经典的「方格取数 / 传纸条」问题，可以用四维 DP 同时描述两条路径的状态。\n状态定义 dp[i][j][k][l] 表示：第一条路径走到 (i, j)，第二条路径走到 (k, l) 时，两条路径的友好指数之和的最大值。\n状态转移 每一步两条路径各走一步，各有两种走法（向下 / 向右），共 4 种组合：\n① dp[i-1][j][k-1][l] 第一条向下，第二条向下 ② dp[i-1][j][k][l-1] 第一条向下，第二条向右 ③ dp[i][j-1][k-1][l] 第一条向右，第二条向下 ④ dp[i][j-1][k][l-1] 第一条向右，第二条向右 取四个方向的最大值，再加上当前格子的友好指数（若两路径处于同一格，只加一次）。\n去重（同一格子只算一次） 如果 i == k \u0026amp;\u0026amp; j == l，说明两条路径当前走到了同一格子，这个格子的友好指数只需加一次。\ndp[i][j][k][l] = best + a[i][j] + a[k][l]; if (i == k \u0026amp;\u0026amp; j == l) { dp[i][j][k][l] -= a[i][j]; // 同一格只算一次 } 初始化 起点 dp[1][1][1][1] 初始化为 a[1][1]，其余初始为负无穷大，表示「不可达」。\n完整代码 C++（四维 DP） #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int kMaxN = 55; const int kNegInf = -0x3f3f3f3f; int dp[kMaxN][kMaxN][kMaxN][kMaxN]; int a[kMaxN][kMaxN]; int m, n; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin \u0026gt;\u0026gt; m \u0026gt;\u0026gt; n; for (int i = 1; i \u0026lt;= m; i++) { for (int j = 1; j \u0026lt;= n; j++) { cin \u0026gt;\u0026gt; a[i][j]; } } // 初始化为负无穷，表示不可达 for (int i = 0; i \u0026lt;= m; i++) { for (int j = 0; j \u0026lt;= n; j++) { for (int k = 0; k \u0026lt;= m; k++) { for (int l = 0; l \u0026lt;= n; l++) { dp[i][j][k][l] = kNegInf; } } } } dp[1][1][1][1] = a[1][1]; for (int i = 1; i \u0026lt;= m; i++) { for (int j = 1; j \u0026lt;= n; j++) { for (int k = 1; k \u0026lt;= m; k++) { for (int l = 1; l \u0026lt;= n; l++) { if (i == 1 \u0026amp;\u0026amp; j == 1 \u0026amp;\u0026amp; k == 1 \u0026amp;\u0026amp; l == 1) continue; int best = max({ dp[i - 1][j][k - 1][l], dp[i - 1][j][k][l - 1], dp[i][j - 1][k - 1][l], dp[i][j - 1][k][l - 1] }); dp[i][j][k][l] = best + a[i][j] + a[k][l]; if (i == k \u0026amp;\u0026amp; j == l) { dp[i][j][k][l] -= a[i][j]; // 同一格只算一次 } } } } } cout \u0026lt;\u0026lt; dp[m][n][m][n] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 四维 DP 的状态转移 int best = max({ dp[i - 1][j][k - 1][l], // 下、下 dp[i - 1][j][k][l - 1], // 下、右 dp[i][j - 1][k - 1][l], // 右、下 dp[i][j - 1][k][l - 1] // 右、右 }); 四种转移分别对应：\n第一条路径向下 (i-1, j)，第二条路径向下 (k-1, l) 第一条路径向下 (i-1, j)，第二条路径向右 (k, l-1) 第一条路径向右 (i, j-1)，第二条路径向下 (k-1, l) 第一条路径向右 (i, j-1)，第二条路径向右 (k, l-1) 同一格去重 if (i == k \u0026amp;\u0026amp; j == l) { dp[i][j][k][l] -= a[i][j]; } 两条路径走到同一个格子时，a[i][j] 被加了两次（a[i][j] + a[k][l] 当 i==k \u0026amp;\u0026amp; j==l 就是 2*a[i][j]），所以需要减去一次。\n复杂度分析 指标 复杂度 时间复杂度 O(m² × n²)，m,n ≤ 50 时约为 6.25 × 10⁶ 空间复杂度 O(m² × n²)，约需 6.25 MB 洛谷数据 m, n ≤ 50，完全在限制范围内。\n易错点 初始化为负无穷：未到达的状态必须初始化为极小值，否则 max() 会错误地取到未更新的 0。\n同一格去重：if (i == k \u0026amp;\u0026amp; j == l) 必须判断，否则同一格子的友好指数会被重复计入。\n起点跳过：dp[1][1][1][1] 已在循环外初始化，循环内应跳过，避免被负无穷覆盖。\n行列边界：i-1、j-1、k-1、l-1 必须保证不小于 1，但由于 dp[0][...] 等初始化为负无穷，不会被选为 best，可以放心使用。\n其他语言解法 Java（四维 DP） import java.io.BufferedInputStream; import java.io.IOException; public class Main { private static final class FastScanner { private final BufferedInputStream in; private final byte[] buffer = new byte[1 \u0026lt;\u0026lt; 16]; private int ptr = 0; private int len = 0; FastScanner() { in = new BufferedInputStream(System.in); } private int readByte() throws IOException { if (ptr \u0026gt;= len) { len = in.read(buffer); ptr = 0; if (len \u0026lt;= 0) return -1; } return buffer[ptr++]; } int nextInt() throws IOException { int c, sign = 1, res = 0; do { c = readByte(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); if (c == \u0026#39;-\u0026#39;) { sign = -1; c = readByte(); } while (c \u0026gt; \u0026#39; \u0026#39;) { res = res * 10 + (c - \u0026#39;0\u0026#39;); c = readByte(); } return res * sign; } } public static void main(String[] args) throws Exception { FastScanner fs = new FastScanner(); int m = fs.nextInt(); int n = fs.nextInt(); int[][] a = new int[m + 1][n + 1]; for (int i = 1; i \u0026lt;= m; i++) { for (int j = 1; j \u0026lt;= n; j++) { a[i][j] = fs.nextInt(); } } final int NEG_INF = -0x3f3f3f3f; int[][][][] dp = new int[m + 1][n + 1][m + 1][n + 1]; for (int i = 0; i \u0026lt;= m; i++) for (int j = 0; j \u0026lt;= n; j++) for (int k = 0; k \u0026lt;= m; k++) for (int l = 0; l \u0026lt;= n; l++) dp[i][j][k][l] = NEG_INF; dp[1][1][1][1] = a[1][1]; for (int i = 1; i \u0026lt;= m; i++) { for (int j = 1; j \u0026lt;= n; j++) { for (int k = 1; k \u0026lt;= m; k++) { for (int l = 1; l \u0026lt;= n; l++) { if (i == 1 \u0026amp;\u0026amp; j == 1 \u0026amp;\u0026amp; k == 1 \u0026amp;\u0026amp; l == 1) continue; int best = Math.max( Math.max(dp[i - 1][j][k - 1][l], dp[i - 1][j][k][l - 1]), Math.max(dp[i][j - 1][k - 1][l], dp[i][j - 1][k][l - 1]) ); dp[i][j][k][l] = best + a[i][j] + a[k][l]; if (i == k \u0026amp;\u0026amp; j == l) { dp[i][j][k][l] -= a[i][j]; } } } } } System.out.println(dp[m][n][m][n]); } } Go（四维 DP） package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strconv\u0026#34; ) func max4(a, b, c, d int) int { return max(max(a, b), max(c, d)) } func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Split(bufio.ScanWords) scanner.Scan() m, _ := strconv.Atoi(scanner.Text()) scanner.Scan() n, _ := strconv.Atoi(scanner.Text()) a := make([][]int, m+1) for i := 0; i \u0026lt;= m; i++ { a[i] = make([]int, n+1) } for i := 1; i \u0026lt;= m; i++ { for j := 1; j \u0026lt;= n; j++ { scanner.Scan() a[i][j], _ = strconv.Atoi(scanner.Text()) } } const NEG_INF = -0x3f3f3f3f dp := make([][][][]int, m+1) for i := 0; i \u0026lt;= m; i++ { dp[i] = make([][][]int, n+1) for j := 0; j \u0026lt;= n; j++ { dp[i][j] = make([][]int, m+1) for k := 0; k \u0026lt;= m; k++ { dp[i][j][k] = make([]int, n+1) for l := 0; l \u0026lt;= n; l++ { dp[i][j][k][l] = NEG_INF } } } } dp[1][1][1][1] = a[1][1] for i := 1; i \u0026lt;= m; i++ { for j := 1; j \u0026lt;= n; j++ { for k := 1; k \u0026lt;= m; k++ { for l := 1; l \u0026lt;= n; l++ { if i == 1 \u0026amp;\u0026amp; j == 1 \u0026amp;\u0026amp; k == 1 \u0026amp;\u0026amp; l == 1 { continue } best := max4( dp[i-1][j][k-1][l], dp[i-1][j][k][l-1], dp[i][j-1][k-1][l], dp[i][j-1][k][l-1], ) dp[i][j][k][l] = best + a[i][j] + a[k][l] if i == k \u0026amp;\u0026amp; j == l { dp[i][j][k][l] -= a[i][j] } } } } } fmt.Println(dp[m][n][m][n]) } func max(a, b int) int { if a \u0026gt; b { return a } return b } JavaScript（四维 DP） \u0026#39;use strict\u0026#39;; const fs = require(\u0026#39;fs\u0026#39;); const data = fs.readFileSync(\u0026#39;/dev/stdin\u0026#39;, \u0026#39;utf8\u0026#39;); const tokens = data.trim().split(/\\s+/).map(Number); let idx = 0; const m = tokens[idx++]; const n = tokens[idx++]; const a = Array.from({ length: m + 1 }, () =\u0026gt; Array(n + 1).fill(0)); for (let i = 1; i \u0026lt;= m; i++) { for (let j = 1; j \u0026lt;= n; j++) { a[i][j] = tokens[idx++]; } } const NEG_INF = -0x3f3f3f3f; const dp = Array.from({ length: m + 1 }, () =\u0026gt; Array.from({ length: n + 1 }, () =\u0026gt; Array.from({ length: m + 1 }, () =\u0026gt; Array(n + 1).fill(NEG_INF) ) ) ); dp[1][1][1][1] = a[1][1]; for (let i = 1; i \u0026lt;= m; i++) { for (let j = 1; j \u0026lt;= n; j++) { for (let k = 1; k \u0026lt;= m; k++) { for (let l = 1; l \u0026lt;= n; l++) { if (i === 1 \u0026amp;\u0026amp; j === 1 \u0026amp;\u0026amp; k === 1 \u0026amp;\u0026amp; l === 1) continue; const best = Math.max( dp[i - 1][j][k - 1][l], dp[i - 1][j][k][l - 1], dp[i][j - 1][k - 1][l], dp[i][j - 1][k][l - 1] ); dp[i][j][k][l] = best + a[i][j] + a[k][l]; if (i === k \u0026amp;\u0026amp; j === l) { dp[i][j][k][l] -= a[i][j]; } } } } } console.log(dp[m][n][m][n]); ","link":"https://www.cosefinch.com/posts/p1006-passing-notes/","section":"posts","tags":["动态规划"],"title":"P1006 传纸条"},{"body":"题目信息 字段 内容 题号 P1216 难度 橙题（普及-） 知识点 动态规划、记忆化搜索 题目描述 观察如下所示的数字三角形：\n7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从顶部（第一行）出发，在每一层可以选择向左下或向右下走（即 (i, j) 只能走到 (i+1, j) 或 (i+1, j+1)）。\n找到一条路径，使路径经过的格子中的数字之和最大，输出这个最大值。\n输入格式：\n第一行一个整数 n，表示数字三角形的行数（1 ≤ n ≤ 1000）。\n接下来 n 行，第 i 行有 i 个整数，表示每行的数字（各数字不超过 10000）。\n输出格式：\n一个整数，表示从顶部到底部的最大路径和。\n样例：\n输入\n5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 输出\n30 样例解释：\n最大路径为 7 → 3 → 8 → 7 → 5，和为 7 + 3 + 8 + 7 + 5 = 30。\n解题思路 核心思想：动态规划 这道题是**动态规划（Dynamic Programming）**的经典入门题。\n动态规划的核心思想：把一个大问题拆成若干小问题，先解决小问题，再用小问题的解推出大问题的解。\n方法一：记忆化递归（自顶向下） 从顶部开始，递归地计算每个位置能得到的最大和。递归过程中用数组缓存结果，避免重复计算。\n设 dp[i][j] 表示从位置 (i, j) 出发到最底层，能够得到的最大路径和。\n递归方程：\ndp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j] 终止条件：当 i == n（到达最后一行）时，dp[i][j] = triangle[i][j]。\n方法二：自底向上递推（更常见） 从最后一行开始，依次向上计算。每一行的每个位置，只需要知道它下方两个位置的最大值即可。\n// 从倒数第二行开始，依次向上递推 for (int i = n - 1; i \u0026gt;= 1; i--) { for (int j = 1; j \u0026lt;= i; j++) { triangle[i][j] += max(triangle[i+1][j], triangle[i+1][j+1]); } } 最后 triangle[1][1] 就是答案。\n为什么从下往上递推可行？ 因为最后一行没有更深的路径，所以 dp[n][j] = triangle[n][j] 是确定的。\n有了最后一行，倒数第二行的每个位置只需要比较它下方两个相邻位置的最大值，再加上自己的值，就得到了从这个位置出发的最大和。\n这个过程不断向上重复，直到顶部。\n完整代码 C++（记忆化递归） #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int MAX_N = 1001; int triangle[MAX_N][MAX_N]; int memo[MAX_N][MAX_N]; int n; int MaxSumFrom(int row, int col) { if (memo[row][col] != -1) { return memo[row][col]; } if (row == n) { memo[row][col] = triangle[row][col]; } else { int left = MaxSumFrom(row + 1, col); int right = MaxSumFrom(row + 1, col + 1); memo[row][col] = max(left, right) + triangle[row][col]; } return memo[row][col]; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin \u0026gt;\u0026gt; n; for (int i = 1; i \u0026lt;= n; i++) { for (int j = 1; j \u0026lt;= i; j++) { cin \u0026gt;\u0026gt; triangle[i][j]; memo[i][j] = -1; } } cout \u0026lt;\u0026lt; MaxSumFrom(1, 1) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } C++（自底向上递推） #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin \u0026gt;\u0026gt; n; const int MAX_N = 1001; int triangle[MAX_N][MAX_N] = {0}; for (int i = 1; i \u0026lt;= n; i++) { for (int j = 1; j \u0026lt;= i; j++) { cin \u0026gt;\u0026gt; triangle[i][j]; } } // 自底向上递推 for (int i = n - 1; i \u0026gt;= 1; i--) { for (int j = 1; j \u0026lt;= i; j++) { triangle[i][j] += max(triangle[i + 1][j], triangle[i + 1][j + 1]); } } cout \u0026lt;\u0026lt; triangle[1][1] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 记忆化递归的核心 int MaxSumFrom(int row, int col) { if (memo[row][col] != -1) { return memo[row][col]; // 已计算过，直接返回 } // ... 计算并缓存结果 memo[row][col] = max(left, right) + triangle[row][col]; return memo[row][col]; } memo 数组用于记录每个位置已经计算过的最大路径和。当递归走到已经计算过的位置时，直接返回缓存结果，避免了大量重复计算。\n自底向上递推的核心 for (int i = n - 1; i \u0026gt;= 1; i--) { for (int j = 1; j \u0026lt;= i; j++) { triangle[i][j] += max(triangle[i + 1][j], triangle[i + 1][j + 1]); } } 从倒数第二行开始，每个位置的值变成「自身 + 下方两个相邻位置中的较大值」。这样一层层向上，最终顶部就变成了整个三角形的最大路径和。\n复杂度分析 方法 时间复杂度 空间复杂度 记忆化递归 O(n²) O(n²) 自底向上递推 O(n²) O(n²)（可优化到 O(n)） 两种方法的时间复杂度相同。自底向上递推的空间可以优化到 O(n)，只需要保存最后一行，然后逐行向上更新。\n易错点 数组下标：本题采用 1-indexed（下标从 1 开始），递归时注意 (i, j) 的相邻位置是 (i+1, j) 和 (i+1, j+1)，不是 (i+1, j-1)。\n边界处理：使用自底向上递推时，循环到 j \u0026lt;= i 即可，因为第 i 行恰好有 i 个元素。\n初始化：使用记忆化递归时，memo 数组必须初始化为一个不可能的值（如 -1），用于标记「未计算过」。\n其他语言解法 Python（自底向上递推） import sys def main(): data = sys.stdin.read().strip().split() if not data: return it = iter(data) n = int(next(it)) triangle = [[0] * (n + 2) for _ in range(n + 2)] for i in range(1, n + 1): for j in range(1, i + 1): triangle[i][j] = int(next(it)) for i in range(n - 1, 0, -1): for j in range(1, i + 1): triangle[i][j] += max(triangle[i + 1][j], triangle[i + 1][j + 1]) print(triangle[1][1]) if __name__ == \u0026#34;__main__\u0026#34;: main() Java（自底向上递推） import java.io.BufferedInputStream; import java.util.Arrays; import java.util.InputMismatchException; import java.util.NoSuchElementException; public class Main { private static final class FastScanner { private final BufferedInputStream in; private final byte[] buffer = new byte[1 \u0026lt;\u0026lt; 16]; private int ptr = 0; private int len = 0; FastScanner() { in = new BufferedInputStream(System.in); } private int readByte() throws java.io.IOException { if (ptr \u0026gt;= len) { len = in.read(buffer); ptr = 0; if (len \u0026lt;= 0) { return -1; } } return buffer[ptr++]; } int nextInt() throws java.io.IOException { int c, sign = 1, res = 0; do { c = readByte(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); if (c == \u0026#39;-\u0026#39;) { sign = -1; c = readByte(); } while (c \u0026gt; \u0026#39; \u0026#39;) { res = res * 10 + (c - \u0026#39;0\u0026#39;); c = readByte(); } return res * sign; } } public static void main(String[] args) throws Exception { FastScanner fs = new FastScanner(); int n = fs.nextInt(); int[][] triangle = new int[n + 2][n + 2]; for (int i = 1; i \u0026lt;= n; i++) { for (int j = 1; j \u0026lt;= i; j++) { triangle[i][j] = fs.nextInt(); } } for (int i = n - 1; i \u0026gt;= 1; i--) { for (int j = 1; j \u0026lt;= i; j++) { triangle[i][j] += Math.max(triangle[i + 1][j], triangle[i + 1][j + 1]); } } System.out.println(triangle[1][1]); } } Go（自底向上递推） package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strconv\u0026#34; ) func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Split(bufio.ScanWords) if !scanner.Scan() { return } n, _ := strconv.Atoi(scanner.Text()) const maxN = 1001 triangle := make([][]int, maxN) for i := 0; i \u0026lt; maxN; i++ { triangle[i] = make([]int, maxN) } for i := 1; i \u0026lt;= n; i++ { for j := 1; j \u0026lt;= i; j++ { if scanner.Scan() { val, _ := strconv.Atoi(scanner.Text()) triangle[i][j] = val } } } for i := n - 1; i \u0026gt;= 1; i-- { for j := 1; j \u0026lt;= i; j++ { triangle[i][j] += max(triangle[i+1][j], triangle[i+1][j+1]) } } fmt.Println(triangle[1][1]) } func max(a, b int) int { if a \u0026gt; b { return a } return b } JavaScript（自底向上递推） \u0026#39;use strict\u0026#39;; const fs = require(\u0026#39;fs\u0026#39;); const data = fs.readFileSync(\u0026#39;/dev/stdin\u0026#39;, \u0026#39;utf8\u0026#39;); const tokens = data.trim().split(/\\s+/).map(Number); let idx = 0; const n = tokens[idx++]; const triangle = Array.from({ length: n + 2 }, () =\u0026gt; Array(n + 2).fill(0) ); for (let i = 1; i \u0026lt;= n; i++) { for (let j = 1; j \u0026lt;= i; j++) { triangle[i][j] = tokens[idx++]; } } for (let i = n - 1; i \u0026gt;= 1; i--) { for (let j = 1; j \u0026lt;= i; j++) { triangle[i][j] += Math.max(triangle[i + 1][j], triangle[i + 1][j + 1]); } } console.log(triangle[1][1]); ","link":"https://www.cosefinch.com/posts/p1216-number-triangle/","section":"posts","tags":["动态规划"],"title":"P1216 数字三角形"},{"body":"题目信息 字段 内容 题号 P7285 难度 红题（入门） 知识点 贪心、模拟 题目描述 给定一个长度为 n、元素只含 0 或 1 的数组。\n你可以进行若干次操作：选择若干个值为 0 的元素，将其修改为 1（不能为 1 改 0）。\n记：\nx = 修改后数组中最长连续 1 子段的长度（若全为 0 则 x = 0） y = 被修改的元素的个数 求 x - y 的最大值，并输出一种能达到该最大值的修改后的数组（如有多种方案，任意输出一种即可）。\n输入格式：\nT （对于每组数据） n a1 a2 ... an 第一行一个整数 T，表示测试数据组数。 每组数据第一行一个整数 n，第二行 n 个整数 a_1 ... a_n（保证为 0 或 1）。\n输出格式： 共 2 × T 行，每 2 行表示一组数据：\n第一行：一个整数，表示 x - y 的最大值； 第二行：n 个整数（0 或 1），表示修改后的数组。 样例输入：\n2 3 0 1 0 5 1 0 0 1 1 样例输出：\n1 1 1 1 3 1 1 1 1 1 数据范围： 1 ≤ T ≤ 10，1 ≤ n ≤ 10^5，各测试点 n 之和不超过 10^5。保证输入均为合法 0 或 1。\n解题思路 核心观察 这道题最妙的地方在于：把某个 0 改成 1，对 x - y 的值没有影响！\n证明很简单：\n修改前：x = L，y = k 把一个 0 改成 1：这个 0 变成 1 后，可能让某段连续 1 变长，假设最长连续 1 子段长度增加了 Δx（Δx ≥ 0） 修改后：x' = x + Δx，y' = y + 1 x' - y' = (x + Δx) - (y + 1) 等等，这样分析比较复杂。换个角度思考：\n关键结论：把所有的 0 都改成 1，是最优策略之一。\n理由：\n改一个 0 为 1：y 增加 1；同时这个 1 可能连接左右两段 1，让 x 增加。 最极限情况：把所有数都改成 1，则 x = n。 此时 x - y = n - (n - {原1的个数}) = {原1的个数}。 这个值恰好等于：不修改任何元素时，x - y = {最长连续1子段长度} 的上界（因为原1的个数 ≥ 最长连续1子段长度）。 实际上可以证明：x - y 的最大值 = 原数组中 1 的总个数。\n证明思路：\n设原数组中 1 的个数为 c_1。 若把所有 0 改成 1：x = n，y = n - c_1，x - y = c_1。 若不做任何修改：x = {最长连续1子段长度} ≤ c_1，y = 0，x - y ≤ c_1。 若只改部分：0 改 1 过程中 x 的增长不超过 y 的增长（每次操作 y 必然 +1，x 至多增加该 0 连接的两侧 1 段长度之和 +1，但这部分 1 原本就存在）。 综上，x - y 的最大值就是 c_1。 算法流程 读入 T 对于每组数据： 读入 n 统计数组中 1 的个数，记为 ans 输出 ans 输出 n 个 1（把数组全部改为 1 是一种合法方案） 时间复杂度：O(T × n)，各测试点 n 之和 ≤ 10^5，完全可行。 空间复杂度：O(1)（无需储存数组，边读边统计即可）。 完整代码（C++） // P7285 修改数组 - 洛谷 // 知识点：贪心、模拟 // 算法：把所有 0 改成 1，x-y 的最大值 = 原数组中 1 的个数 // 作者：朱雀 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin \u0026gt;\u0026gt; t; while (t--) { int n; cin \u0026gt;\u0026gt; n; int ans = 0; // 原数组中 1 的个数 for (int i = 0; i \u0026lt; n; i++) { int x; cin \u0026gt;\u0026gt; x; if (x == 1) { ans++; } } // 输出 x-y 的最大值 cout \u0026lt;\u0026lt; ans \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; // 输出修改后的数组（全为 1 是一种合法方案） for (int i = 0; i \u0026lt; n; i++) { cout \u0026lt;\u0026lt; \u0026#34;1 \u0026#34;; } cout \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } return 0; } 关键代码讲解 为什么要统计「1 的个数」？ 这是本题最巧妙的数学结论：把任意个 0 改成 1 后，x - y 的值不会改变（或者说，最大值恰好等于原数组 1 的个数）。\n直观理解：每改一个 0→1，你付出了 1 的代价（y 增加 1），但同时可能让连续 1 段变长。最理想情况是全改成 1，此时 x = n，y = n - c_1，相减得 c_1。\n为什么不需要存数组？ 因为只需要知道 1 的个数，不需要根据原数组构造答案（全输出 1 就是合法方案）。所以边读边统计即可，空间复杂度 O(1)。\n易错点 / 注意事项 ⚠️ 每组数据输出 2 行：第一行是 x-y 的最大值，第二行是修改后的数组。 ⚠️ 末尾空格：洛谷一般允许行末有空格，但最好养成习惯，最后一个元素单独处理（本题不要求，输出 1 重复 n 次后换行即可）。 ⚠️ 多测重置：用 while (t--) 结构自然重置，不要依赖全局变量。 ⚠️ 用 \\n 而非 endl：防止大量输出时频繁刷新缓冲区。 其他语言解法 Python Python 代码如下（遵循 PEP 8 规范）：\n# P7285 修改数组 - 洛谷 # 知识点：贪心、模拟 # 算法：把所有 0 改成 1，x-y 的最大值 = 原数组中 1 的个数 def solve() -\u0026gt; None: t = int(input().strip()) for _ in range(t): n = int(input().strip()) arr = list(map(int, input().split())) ans = sum(arr) # 1 的个数 print(ans) print(\u0026#34; \u0026#34;.join([\u0026#34;1\u0026#34;] * n)) if __name__ == \u0026#34;__main__\u0026#34;: solve() Java ⚠️ 重要：Scanner 在洛谷等 OJ 平台下内存占用极高，容易 MLE。请务必使用 BufferedInputStream 手动解析输入。\n// P7285 修改数组 - 洛谷 // 知识点：贪心、模拟 // 算法：把所有 0 改成 1，x-y 的最大值 = 原数组中 1 的个数 import java.io.*; import java.util.*; public class Main { // 高速字节流输入，避免 Scanner 的高内存开销 private static final class FastScanner { private final InputStream in; private final byte[] buffer = new byte[1 \u0026lt;\u0026lt; 16]; private int ptr = 0, len = 0; FastScanner(InputStream in) { this.in = in; } private int readByte() throws IOException { if (ptr \u0026gt;= len) { len = in.read(buffer); ptr = 0; if (len \u0026lt;= 0) return -1; } return buffer[ptr++]; } int nextInt() throws IOException { int c, sign = 1, val = 0; do { c = readByte(); } while (c \u0026lt;= \u0026#39; \u0026#39; \u0026amp;\u0026amp; c != -1); if (c == \u0026#39;-\u0026#39;) { sign = -1; c = readByte(); } while (c \u0026gt; \u0026#39; \u0026#39;) { val = val * 10 + (c - \u0026#39;0\u0026#39;); c = readByte(); } return val * sign; } } public static void main(String[] args) throws Exception { FastScanner fs = new FastScanner(System.in); StringBuilder out = new StringBuilder(); int t = fs.nextInt(); while (t-- \u0026gt; 0) { int n = fs.nextInt(); int ans = 0; for (int i = 0; i \u0026lt; n; i++) { if (fs.nextInt() == 1) { ans++; } } out.append(ans).append(\u0026#39;\\n\u0026#39;); for (int i = 0; i \u0026lt; n; i++) { out.append(\u0026#39;1\u0026#39;); if (i \u0026lt; n - 1) out.append(\u0026#39; \u0026#39;); } out.append(\u0026#39;\\n\u0026#39;); } System.out.print(out.toString()); } } Go Go 代码如下（遵循 gofmt 规范）：\n// P7285 修改数组 - 洛谷 // 知识点：贪心、模拟 // 算法：把所有 0 改成 1，x-y 的最大值 = 原数组中 1 的个数 package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;os\u0026#34; \u0026#34;strings\u0026#34; ) func main() { in := bufio.NewReader(os.Stdin) out := bufio.NewWriter(os.Stdout) defer out.Flush() var t int fmt.Fscan(in, \u0026amp;t) for i := 0; i \u0026lt; t; i++ { var n int fmt.Fscan(in, \u0026amp;n) ans := 0 for j := 0; j \u0026lt; n; j++ { var x int fmt.Fscan(in, \u0026amp;x) if x == 1 { ans++ } } fmt.Fprintln(out, ans) fmt.Fprintln(out, strings.Repeat(\u0026#34;1 \u0026#34;, n-1)+\u0026#34;1\u0026#34;) } } JavaScript (Node.js) JavaScript 代码如下：\n// P7285 修改数组 - 洛谷 // 知识点：贪心、模拟 // 算法：把所有 0 改成 1，x-y 的最大值 = 原数组中 1 的个数 const readline = require(\u0026#34;readline\u0026#34;); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); let t = -1; let currentCase = 0; let n = 0; rl.on(\u0026#34;line\u0026#34;, function (line) { if (t \u0026lt; 0) { t = parseInt(line.trim()); return; } if (n === 0) { n = parseInt(line.trim()); return; } const arr = line.trim().split(\u0026#34; \u0026#34;).map(Number); const ans = arr.reduce((sum, x) =\u0026gt; sum + x, 0); // 1 的个数 console.log(ans); console.log(\u0026#34;1 \u0026#34;.repeat(n).trim()); currentCase++; if (currentCase \u0026lt; t) { n = 0; // 准备读下一组的 n } }); 附：算法概念介绍 贪心算法（Greedy Algorithm） 什么是贪心？\n贪心算法的核心思想是：每一步都做出当前看起来最优的选择，期望最终得到全局最优解。\n就像一个贪心的人挑水果——每看到一个比别人手里更好的，就立刻换掉手里那个，不管以后会不会后悔。\n经典例子：找零问题\n用最少的纸币凑出 37 元，面值有 1、5、20、50 元。\n贪心策略：每次都选不超过剩余金额的最大面值。\n37 → 选 20，剩 17 17 → 选 5，剩 12 → 再选 5，剩 7 7 → 选 5，剩 2 2 → 选 1，剩 1 → 再选 1 用掉：20 + 5 + 5 + 5 + 1 + 1 = 6 张（最优）。\n贪心一定对吗？\n不一定！贪心不保证全局最优，只有在问题满足「贪心选择性质」时才成立。\n反例：如果面值改为 1、15、25 元，凑 30 元：\n贪心：25 + 1×5 = 6 张 最优：15 × 2 = 2 张 本题为什么不是贪心？\n我们直接推导出「全改成 1」是最优解，没有\u0026quot;一步步做选择\u0026quot;的过程，所以不属于贪心算法。\n模拟算法（Simulation） 什么是模拟？\n模拟算法是最\u0026quot;老实\u0026quot;的算法——题目让你做什么，你就一步步照着做，不偷懒、不找捷径。\n就像按照菜谱做菜：第一步切菜、第二步下油、第三步翻炒……严格按步骤来，最后端出来的菜就是对题目的\u0026quot;模拟\u0026quot;。\n经典例子：约瑟夫问题\nn 个人围成一圈，从第一个人开始报数，报到 k 的人出圈，下一个人重新从 1 开始报数。求最后剩下的人。\n模拟做法：用一个数组记录每个人是否还在圈内，每次找到第 k 个还在的人，将其标记为出圈，直到只剩一个人。\n模拟的优缺点\n优点 缺点 思路直接，不容易写错 如果数据规模大，可能超时 适合入门，几乎所有题都能\u0026quot;硬算\u0026quot; 不是优雅的解法 本题为什么不是模拟？\n模拟的做法应该是：真的去\u0026quot;修改数组\u0026quot;，每次把一个 0 改成 1，然后计算新的 x 和 y，比较所有方案……但这太慢了（指数级）。我们跳过了模拟过程，直接数学推导，所以也不是模拟。\n","link":"https://www.cosefinch.com/posts/p7285-modify-array/","section":"posts","tags":["数学"],"title":"P7285 修改数组"},{"body":"","link":"https://www.cosefinch.com/categories/%E5%85%A5%E9%97%A8/","section":"categories","tags":null,"title":"入门"},{"body":"","link":"https://www.cosefinch.com/tags/%E6%95%B0%E5%AD%A6/","section":"tags","tags":null,"title":"数学"},{"body":"","link":"https://www.cosefinch.com/tags/ascii/","section":"tags","tags":null,"title":"ASCII"},{"body":"题目信息 字段 内容 题号 P7106 难度 红题（入门） 知识点 ASCII、字符与数值互转 题目描述 给定一个十六进制颜色码，输出其反色的十六进制颜色码。\n颜色码格式形如 #EBA932，由 7 个字符组成：\n第 1 位：#，固定不变。 第 2~3 位：R 值（红色分量），用两位十六进制数表示。 第 4~5 位：G 值（绿色分量），用两位十六进制数表示。 第 6~7 位：B 值（蓝色分量），用两位十六进制数表示。 十六进制数码共 16 个，从小到大依次为：0 1 2 3 4 5 6 7 8 9 A B C D E F。\n对于颜色 (r, g, b)，其反色定义为 (255-r, 255-g, 255-b)。\n输入格式： 一行，长度为 7 的合法十六进制颜色码字符串。\n输出格式： 一行，长度为 7 的反色十六进制颜色码字符串。\n样例：\n样例输入 样例输出 #FFFFFF #000000 #EBA932 #1456CD 样例解释：\n以 #FFFFFF 为例，RGB 值为 (255, 255, 255)，反色为 (0, 0, 0)，即 #000000。\n以 B 值 32 为例：(32)_{16} = 50，反色 = 255 - 50 = 205 = (CD)_{16}。\n数据范围： 本题共 10 个测试点，保证输入为合法十六进制颜色码。\n解题思路 先理解「反色」到底在算什么 十六进制颜色码由 3 组「两位十六进制数」构成，每组代表一个 0~255 的颜色分量。\n求反色 = 对每个分量求 255 - x。\n而 255 用十六进制表示恰好是 FF，即求 {FF} - {每位数码}。\n关键发现：{FF} - xy = (15 - x)(15 - y)\n也就是说，不用把两位十六进制数先转成十进制再算，直接*对每一位数码单独做「15 减法」*就行！\n举例：E 在十六进制中是 14，15 - 14 = 1，对应数码 1。\n再举：B 是 11，15 - 11 = 4，对应数码 4。\n所以 EB 的反色就是 14 ✓（和样例吻合）。\n算法核心：字符 ↔ 数值互转 题目的难点只有一个：怎么把十六进制数码字符（'0'~'9'，'A'~'F'）和它代表的数值（0~15）相互转换？\n答案是利用 ASCII 码：\n字符 ASCII 码 代表的数值 '0' 48 0 '9' 57 9 'A' 65 10 'F' 70 15 可以发现：\n'0'~'9'：数值 = c - '0' 'A'~'F'：数值 = c - 'A' + 10，也等于 c - '0' - 7（因为 'A' - '0' = 17，减去 7 得 10） 反过来，数值 → 字符：\n0~9：字符 = v + '0' 10~15：字符 = v + 'A' - 10，也等于 v + '0' + 7 代码中用常量 kAsciiGap = 7 来表示这个偏移量，kMaxDigit = 15 代表十六进制单位最大值。\n算法流程 读入颜色码（7 个字符） 第 1 位 \u0026#39;#\u0026#39; 直接输出 对第 2~7 位每个字符： 字符 → 数值（0~15） 数值 = 15 - 数值 数值 → 字符 输出该字符 时间复杂度：O(1)（固定处理 6 位数码） 空间复杂度：O(1) 完整代码 // P7106 双生独白 - 洛谷 // 知识点：ASCII 字符与十六进制数码互转 // 算法：对每位十六进制数码单独做「15 减法」，利用 ASCII 偏移转换 // 作者：朱雀 #include \u0026lt;iostream\u0026gt; using namespace std; // 两段 ASCII 区间之间的偏移量：\u0026#39;A\u0026#39;(65) - \u0026#39;9\u0026#39;(57) - 1 = 7 const int kAsciiGap = 7; // 单个十六进制数码的最大值（F = 15） const int kMaxDigit = 15; // 十六进制数码字符 → 对应的整数值（0~15） int CharToVal(char c) { if (c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39;) { return c - \u0026#39;0\u0026#39;; } // \u0026#39;A\u0026#39;~\u0026#39;F\u0026#39;：减去 \u0026#39;0\u0026#39; 后还需减去间隔 7 return c - \u0026#39;0\u0026#39; - kAsciiGap; } // 整数值（0~15）→ 对应的十六进制数码字符 char ValToChar(int v) { if (v \u0026lt;= 9) { return v + \u0026#39;0\u0026#39;; } // 10~15：加上 \u0026#39;0\u0026#39; 后还需加上间隔 7 return v + \u0026#39;0\u0026#39; + kAsciiGap; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); char color_code[7]; for (int i = 0; i \u0026lt; 7; i++) { cin \u0026gt;\u0026gt; color_code[i]; } // 第 1 位 \u0026#39;#\u0026#39; 直接输出，不参与计算 cout \u0026lt;\u0026lt; color_code[0]; // 对第 2~7 位，逐位做「15 - 数码值」运算 for (int i = 1; i \u0026lt; 7; i++) { cout \u0026lt;\u0026lt; ValToChar(kMaxDigit - CharToVal(color_code[i])); } cout \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; return 0; } 关键代码讲解 kAsciiGap = 7 的由来 在 ASCII 表中，'9' 的编码是 57，'A' 的编码是 65，两者之间相差 8，但 '9' 和 'A' 之间还有 :;\u0026lt;=\u0026gt;?@ 七个字符（并非数字也非大写字母），所以从 '9' 跳到 'A' 需要跨过 7 个非数码字符。\nCharToVal('A') = 'A' - '0' - 7 = 65 - 48 - 7 = 10 ✓ ValToChar(10) = 10 + '0' + 7 = 10 + 48 + 7 = 65 = 'A' ✓ 为什么不需要把两位十六进制数合并再算？ 255 的十六进制是 {FF}，而 {FF} - {XY}（两位十六进制数）恰好等于 (15-X)(15-Y)。每一位可以独立计算，完全不需要先合并成十进制。这是本题最核心的数学技巧。\n为什么加了 ios::sync_with_stdio(false)？ 虽然本题数据量极小，但这是标准 C++ 竞赛模板的一部分。关闭 C/C++ 流同步后，cin/cout 速度会大幅提升，在处理大量输入时有明显效果。养成习惯，无害。\n易错点 / 注意事项 ⚠️ # 要原样输出：第一个字符是 #，不参与任何计算，忘记输出就会 WA。 ⚠️ 大写字母：题目明确说字母均为大写，CharToVal 和 ValToChar 只处理大写 A~F，不要混入小写。 ⚠️ 别用 map 硬编码映射：有些同学会用 map\u0026lt;char, char\u0026gt; 枚举所有 16 种互换关系，虽然也能 AC，但远不如 ASCII 偏移简洁，也不利于理解。 ⚠️ 末尾换行：输出完 7 个字符后记得换行，部分测试点对此敏感。 其他语言解法 以下各语言解法思路完全一致：逐位做「15 减法」，仅写法不同。均遵循对应语言的 Google 开发规范。\nPython Python 内置 int(c, 16) 可直接将十六进制字符转为数值，format(v, 'X') 可将数值转回大写十六进制字符，无需手动处理 ASCII 偏移，代码极为简洁。\n# P7106 双生独白 - 洛谷 # 知识点：十六进制字符与数值互转 # 算法：对每位数码单独做「15 减法」 _MAX_DIGIT = 15 def char_to_val(c: str) -\u0026gt; int: \u0026#34;\u0026#34;\u0026#34;十六进制数码字符 → 整数值（0~15）。\u0026#34;\u0026#34;\u0026#34; return int(c, 16) def val_to_char(v: int) -\u0026gt; str: \u0026#34;\u0026#34;\u0026#34;整数值（0~15）→ 大写十六进制数码字符。\u0026#34;\u0026#34;\u0026#34; return format(v, \u0026#39;X\u0026#39;) def main() -\u0026gt; None: color_code = input().strip() result = [color_code[0]] # \u0026#39;#\u0026#39; 原样保留 for ch in color_code[1:]: result.append(val_to_char(_MAX_DIGIT - char_to_val(ch))) print(\u0026#39;\u0026#39;.join(result)) if __name__ == \u0026#39;__main__\u0026#39;: main() Java Java 同样可以用 Character.digit(c, 16) 和 Character.forDigit(v, 16) 处理十六进制字符，但为了与 C++ 思路对应，这里手动实现转换函数，便于理解 ASCII 偏移原理。\n⚠️ 洛谷 Java 提交规范：洛谷要求公开类名必须为 Main，否则编译器找不到入口类会直接报错。\n// P7106 双生独白 - 洛谷 // 知识点：ASCII 字符与十六进制数码互转 // 算法：对每位十六进制数码单独做「15 减法」 import java.util.Scanner; public class Main { private static final int ASCII_GAP = 7; private static final int MAX_DIGIT = 15; /** 十六进制数码字符 → 整数值（0~15）。 */ private static int charToVal(char c) { if (c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39;) { return c - \u0026#39;0\u0026#39;; } return c - \u0026#39;0\u0026#39; - ASCII_GAP; } /** 整数值（0~15）→ 大写十六进制数码字符。 */ private static char valToChar(int v) { if (v \u0026lt;= 9) { return (char) (v + \u0026#39;0\u0026#39;); } return (char) (v + \u0026#39;0\u0026#39; + ASCII_GAP); } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String colorCode = scanner.next(); StringBuilder result = new StringBuilder(); result.append(colorCode.charAt(0)); // \u0026#39;#\u0026#39; 原样保留 for (int i = 1; i \u0026lt; colorCode.length(); i++) { result.append(valToChar(MAX_DIGIT - charToVal(colorCode.charAt(i)))); } System.out.println(result); } } Go Go 遵循 Google 内部风格（即官方 gofmt 规范）：包名小写，函数名 PascalCase（导出）或 camelCase（包内），用 fmt.Scan 读取输入。\n// P7106 双生独白 - 洛谷 // 知识点：ASCII 字符与十六进制数码互转 // 算法：对每位十六进制数码单独做「15 减法」 package main import \u0026#34;fmt\u0026#34; const ( asciiGap = 7 // \u0026#39;9\u0026#39; 与 \u0026#39;A\u0026#39; 之间非数码字符的数量 maxDigit = 15 // 单个十六进制数码的最大值 ) // charToVal 将十六进制数码字符转换为整数值（0~15）。 func charToVal(c byte) int { if c \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;9\u0026#39; { return int(c - \u0026#39;0\u0026#39;) } return int(c-\u0026#39;0\u0026#39;) - asciiGap } // valToChar 将整数值（0~15）转换为大写十六进制数码字符。 func valToChar(v int) byte { if v \u0026lt;= 9 { return byte(v) + \u0026#39;0\u0026#39; } return byte(v) + \u0026#39;0\u0026#39; + asciiGap } func main() { var colorCode string fmt.Scan(\u0026amp;colorCode) result := make([]byte, len(colorCode)) result[0] = colorCode[0] // \u0026#39;#\u0026#39; 原样保留 for i := 1; i \u0026lt; len(colorCode); i++ { result[i] = valToChar(maxDigit - charToVal(colorCode[i])) } fmt.Println(string(result)) } JavaScript (Node.js) JavaScript 可直接用 parseInt(c, 16) 和 v.toString(16).toUpperCase() 完成转换，比手动 ASCII 偏移更简洁。以下代码在 Node.js 环境下读取标准输入。\n// P7106 双生独白 - 洛谷 // 知识点：十六进制字符与数值互转 // 算法：对每位数码单独做「15 减法」 \u0026#39;use strict\u0026#39;; const MAX_DIGIT = 15; /** * 将十六进制数码字符转换为整数值（0~15）。 * @param {string} c - 单个十六进制数码字符 * @returns {number} */ function charToVal(c) { return parseInt(c, 16); } /** * 将整数值（0~15）转换为大写十六进制数码字符。 * @param {number} v - 0~15 的整数 * @returns {string} */ function valToChar(v) { return v.toString(16).toUpperCase(); } /** * 计算十六进制颜色码的反色。 * @param {string} colorCode - 形如 \u0026#39;#RRGGBB\u0026#39; 的颜色码 * @returns {string} 反色颜色码 */ function invertColor(colorCode) { let result = colorCode[0]; // \u0026#39;#\u0026#39; 原样保留 for (let i = 1; i \u0026lt; colorCode.length; i++) { result += valToChar(MAX_DIGIT - charToVal(colorCode[i])); } return result; } // 读取标准输入（适用于 OJ 环境） const lines = []; process.stdin.on(\u0026#39;data\u0026#39;, (data) =\u0026gt; lines.push(data.toString().trim())); process.stdin.on(\u0026#39;end\u0026#39;, () =\u0026gt; { console.log(invertColor(lines[0])); }); ","link":"https://www.cosefinch.com/posts/p7106-dual-monologue/","section":"posts","tags":["ASCII"],"title":"P7106 双生独白"},{"body":"","link":"https://www.cosefinch.com/series/","section":"series","tags":null,"title":"Series"}]