news 2026/8/23 9:22:26

前缀和与哈希表在子数组和问题中的应用:以Codeforces经典题为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前缀和与哈希表在子数组和问题中的应用:以Codeforces经典题为例

1. 项目概述:从一道经典CF题看前缀和与哈希表的威力

最近在Codeforces上刷题,又翻到了ICM Technex 2017和Codeforces Round #400 (Div. 1 + Div. 2, combined)这场比赛的C题,题目叫“Molly‘s Chemicals”。这道题可以说是前缀和思想结合哈希表(或者叫字典、映射)应用的典范,也是很多人在学习算法时从“暴力枚举”思维转向“高效优化”思维的一道分水岭题目。我见过不少朋友卡在这里,明明感觉思路对了,但一提交就是超时(TLE),或者被一些边界条件搞得焦头烂额。今天,我就结合自己多次AC(Accepted)的经验,以及辅导别人时遇到的常见误区,来彻底拆解这道题。

简单来说,这道题给你一个长度为n的整数数组,以及一个整数k。题目要求你找出这个数组中有多少个子数组(连续的一段),其所有元素之和恰好等于k的某个整数次幂(包括k^0 = 1)。这里的k可以是正数、负数,甚至是0(虽然0有特殊情况)。n最大可以到10^5,这意味着O(n^2)的暴力枚举所有子数组的方法肯定行不通,必须找到O(n log n)甚至O(n)的解法。这道题的核心,就在于如何利用前缀和快速计算任意子数组的和,以及如何利用哈希表来高效地“记住”之前出现过的前缀和,从而将两重循环优化到一重。无论你是正在备赛的选手,还是想巩固前缀和与哈希表知识点的学习者,吃透这道题都能让你对这类“子数组和”问题的处理能力提升一个档次。

2. 核心思路与算法设计拆解

2.1 问题重述与暴力解法分析

首先,我们把问题用更形式化的语言描述一下。给定数组a[1..n]和整数k,我们需要统计满足以下条件的子数组a[l..r](1 ≤ l ≤ r ≤ n) 的数量: 子数组和sum(l, r) = a[l] + a[l+1] + ... + a[r]等于k^t,其中t是一个非负整数(t ≥ 0)。

最直观的想法就是暴力枚举。我们可以用两层循环,外层循环枚举子数组的起点l,内层循环枚举子数组的终点r,同时计算从lr的元素和。这样时间复杂度是O(n^3)(因为每次计算和需要O(r-l+1)的时间)。稍微优化一下,在内层循环中累加元素,可以将计算和的过程优化掉,变成O(n^2)。伪代码如下:

count = 0 for l in range(n): current_sum = 0 for r in range(l, n): current_sum += a[r] # 检查 current_sum 是否是 k 的幂 if is_power_of_k(current_sum, k): count += 1

对于n = 10^5O(n^2)意味着大约10^10次操作,这在标准的1秒或2秒时限内是绝对无法通过的。因此,我们必须寻找更优的解法。

2.2 关键转化:前缀和与公式推导

优化的突破口在于前缀和。我们定义前缀和数组prefix[i] = a[1] + a[2] + ... + a[i],为了方便,通常令prefix[0] = 0。那么,子数组a[l..r]的和可以表示为:sum(l, r) = prefix[r] - prefix[l-1]

我们的目标是sum(l, r) = k^t。代入上式,得到:prefix[r] - prefix[l-1] = k^t

移项后,可以得到一个非常关键的等式:prefix[r] - k^t = prefix[l-1]

这个等式的意义是什么?对于当前我们遍历到的位置r(把它当作子数组的右端点),它的前缀和是prefix[r]。我们想知道,有多少个左端点l(实际上对应的是l-1,也就是某个前缀和的下标)满足上面的等式。换句话说,对于当前的prefix[r]和某个k^t,我们需要快速知道在r之前(即下标小于r的位置),前缀和值等于prefix[r] - k^t出现了多少次。

这立刻让我们联想到哈希表(在Python中是字典dict,在C++中是unordered_map,在Java中是HashMap)。我们可以在遍历数组的过程中,用一个哈希表cnt来记录从开始到当前位置之前,每个前缀和值出现的次数。当我们处理到位置i时(此时prefix[i]是当前前缀和):

  1. 我们枚举所有可能的k^t(记为power)。
  2. 对于每个power,我们计算target = prefix[i] - power
  3. 然后查询哈希表cnttarget这个值出现了多少次。这个次数,就等于以i为右端点,子数组和等于power的子数组数量。
  4. 将这些数量累加到答案中。
  5. 最后,将当前前缀和prefix[i]加入到哈希表cnt中,为后续的位置i+1做准备。

这样,我们只需要遍历一次数组(O(n)),对于每个位置i,枚举所有可能的k^t。如果k^t的数量是有限的,那么总时间复杂度就是O(n * m),其中mk^t的个数。

2.3 关于 k^t 枚举范围的确定

这里就引出了本题的第一个难点和易错点:k^t应该枚举到多大?由于数组元素和前缀和都可能很大(题目中元素绝对值可达10^9),k^t也可能增长得非常快或非常慢。

我们需要一个合理的枚举上界。思考一下,子数组的和sum(l, r)最大可能值是多少?最极端情况,所有10^5个元素都是10^9,那么和大约是10^14。实际上,由于前缀和可能为负,我们关心的k^t的绝对值也应该在这个数量级附近。但k^t增长是指数级的:

  • 如果|k| > 1k^t会快速增长。例如k=22^50就已经超过10^15了,所以t枚举到50左右就远超可能的前缀和范围了。
  • 如果|k| = 1,那么k^t永远是1(当k=1) 或1-1交替 (当k=-1)。这是一个特例,需要单独处理。
  • 如果k = 0,那么k^tt>=1时都是0,只有k^0 = 1。这也是一个特例。
  • 如果|k| < 1k != 0,例如k=0.5?注意题目输入是整数,k也是整数。所以|k|最小为0(特例)或1。

因此,在代码实现中,我们通常采用以下策略:

  1. 预先计算出所有可能的、在数据范围内的k^t值,存储在一个列表里。
  2. 计算时,使用一个while循环,当abs(power)超过一个预设的极大值(例如10^15)时停止。同时,为了避免无限循环,当k1-10时,需要特殊处理。

一个稳健的实现方式是:在循环中计算power,如果abs(power)已经大于10^14(一个比最大可能前缀和还大的数),就跳出循环。同时,对于k=1,可能的power只有1;对于k=-1,可能的power只有1-1;对于k=0,可能的power只有010^0在数学上有时定义为1,但在此题上下文和测试数据中,通常认为0^0=1)。我们必须仔细处理这些边界,否则容易漏算或多算。

注意:这里有一个非常隐蔽的坑。当k=1k=-1时,power的值是有限的几个,但如果我们用普通的循环条件while abs(power) <= LIMIT,可能会因为power恒为1±1而导致死循环。所以必须特殊判断,或者使用一个集合(Set)来存储所有已生成的power,如果发现重复,就停止生成。

3. 算法实现与代码细节解析

3.1 数据结构选择与初始化

我们选择哈希表来记录前缀和的出现次数。键(Key)是前缀和的值,值(Value)是该前缀和值到目前为止出现的次数。在遍历开始前,我们需要初始化哈希表,并放入一个关键的键值对:{0: 1}。这是因为前缀和prefix[0] = 0,它对应的是空数组(左端点l=1时,l-1=0)。当我们计算一个从第一个元素开始的子数组(即l=1)时,需要用到prefix[0]

例如,如果prefix[r]本身就是一个k^t,那么根据公式prefix[r] - k^t = prefix[l-1],此时k^t = prefix[r],所以target = prefix[r] - prefix[r] = 0。我们需要知道prefix[l-1] = 0出现了多少次,而l-1可以是0(对应子数组从第一个元素开始)。所以prefix[0]=0必须被计入。

from collections import defaultdict def solve(): n, k = map(int, input().split()) arr = list(map(int, input().split())) # 哈希表,记录某个前缀和值出现的次数 prefix_count = defaultdict(int) prefix_count[0] = 1 # 初始化,非常重要! current_prefix_sum = 0 answer = 0

3.2 幂次枚举的逻辑实现

接下来是实现k^t的枚举。我们需要考虑k的各种情况。

# 预先计算所有可能的 k^t,避免在循环中重复计算 powers = set() power = 1 # 设定一个上限,防止无限循环或溢出 # 考虑到前缀和最大可能约为 10^14 (10^5 * 10^9),这里取大一些 LIMIT = 10**15 if k == 1: powers = {1} elif k == -1: powers = {1, -1} elif k == 0: powers = {0, 1} # 0^0 视为1, 0^1, 0^2... 都是0 else: while abs(power) <= LIMIT: powers.add(power) power *= k # 对于非1、-1、0的k,幂增长很快,集合大小很小(通常<60)

这里我使用了集合powers来存储所有可能的k^t值。使用集合可以自动去重,对于k=1k=-1的情况尤其方便。对于一般的kwhile循环会在power的绝对值超过LIMIT时停止。由于是指数增长,这个集合的大小通常不会超过60(因为2^60已经很大了)。

3.3 主循环与答案统计

现在进入主循环,遍历数组的每个元素,更新当前前缀和,并针对每个可能的power进行查询。

for num in arr: current_prefix_sum += num # 对于每一个可能的 k^t (power) for power in powers: target = current_prefix_sum - power # 查询哈希表,有多少个之前的前缀和等于 target answer += prefix_count.get(target, 0) # 将当前前缀和加入哈希表,供后面的位置查询 prefix_count[current_prefix_sum] += 1 print(answer)

这段代码清晰体现了我们的核心思路:

  1. current_prefix_sumprefix[r]
  2. 对于每个power(k^t),计算target = prefix[r] - power
  3. prefix_count哈希表中查找target出现的次数,累加到答案answer。这对应了所有以r为右端点,子数组和等于power的情况。
  4. 循环结束后,将prefix[r]加入哈希表,这样当遍历到后面的位置时,它就成了“之前的前缀和”。

这个算法的时间复杂度是O(n * m),其中mpowers集合的大小。在n=10^5m<60的情况下,运算量在千万级别,是完全可以接受的。

3.4 处理大数溢出与精度问题

虽然Python的整数可以无限大,不存在溢出问题,但如果我们用C++或Java实现,就需要特别注意。在计算power *= k时,power可能会超过64位整型的范围(long long),导致溢出。溢出后,while循环的判断条件abs(power) <= LIMIT可能失效(例如溢出变成负数,绝对值可能又小于LIMIT),导致死循环。

在C++中,一个安全的做法是在乘法之前进行判断:

long long power = 1; set<long long> powers; if (k == 1) powers = {1}; else if (k == -1) powers = {1, -1}; else if (k == 0) powers = {0, 1}; else { while (abs(power) <= LIMIT) { powers.insert(power); // 防止乘法溢出 if (abs(power) > LIMIT / abs(k)) break; // 再乘一次就会溢出 power *= k; } }

另一个细节是,前缀和current_prefix_sum本身也可能很大。在统计答案answer时,最坏情况下答案可能超过32位整数范围(例如所有子数组都符合条件,数量级是n^2),所以answer应该使用64位整型(C++中的long long,Python中自动支持)。

4. 边界条件与特例深度剖析

4.1 k = 0 的情况

这是最容易出错的情况之一。根据定义:

  • k^0 = 1(通常这样约定)
  • 对于t >= 10^t = 0

所以,合法的power值集合是{1, 0}。这意味着我们只寻找和为1或和为0的子数组。

但这里有一个陷阱:0的幂次0^tt=0时是1,在t>0时是0。它们是两个不同的t对应的值,但都合法。在我们的算法中,powers集合{0, 1}包含了这两个值。算法会正常执行,寻找prefix[r] - 1prefix[r] - 0(即prefix[r]本身)在之前出现的次数。

所以,对于k=0,我们的算法逻辑依然是正确的。只需要注意在生成powers集合时不要漏掉0

4.2 k = 1 或 k = -1 的情况

k=1时,powers = {1}。我们只需要寻找和为1的子数组。算法退化为一个经典问题:给定数组,有多少个子数组的和等于特定值target=1?我们的前缀和+哈希表方法完美解决。

k=-1时,powers = {1, -1}。我们需要寻找和为1或和为-1的子数组。算法同样适用。

这里的关键点在于:对于k=1k=-1,如果我们错误地使用了通用的while循环来生成powers,会因为power值不变(k=1)或只在两个值之间振荡(k=-1)而导致死循环。所以必须像前面代码那样进行特判。

4.3 前缀和可能非常大带来的哈希表问题

在Python中,字典的键可以是任意大的整数,没问题。但在一些语言中,如果使用基于哈希的数据结构,键值过大可能需要考虑哈希函数和冲突。不过,对于这道题的数据范围,主流语言的unordered_mapHashMap都能很好地处理10^14量级的整数作为键。

一个更隐蔽的问题是,当我们用current_prefix_sum - power计算target时,target的值可能超出64位整型范围吗?在本题约束下,数组元素和k都是10^9量级,powerk^tt不会太大(因为增长快),所以target也在10^14量级,64位整型(最大值约9e18)是足够的。

4.4 空子数组是否计入?

题目描述中,子数组定义为1 ≤ l ≤ r ≤ n。这意味着lr可以相等(单个元素),但l不能大于r。空子数组(即没有元素)通常不被认为是一个合法的子数组。在我们的算法中,初始化prefix_count[0] = 1代表的是空前缀(下标0)。当我们计算一个从第一个元素开始的子数组(l=1)时,会用到这个0。这并没有计入空子数组,而是为所有以第一个元素为起点的子数组提供了合法的左端点前缀prefix[0]。所以我们的处理是正确的。

5. 完整代码实现与测试用例

将上述所有部分整合起来,下面是一个完整的Python解决方案,包含了详细的注释和健壮的特例处理。

from collections import defaultdict import sys def solve(): data = sys.stdin.read().strip().split() if not data: return it = iter(data) n = int(next(it)) k = int(next(it)) arr = [int(next(it)) for _ in range(n)] # 哈希表,记录前缀和出现的次数 prefix_count = defaultdict(int) prefix_count[0] = 1 # 空前缀 current_prefix_sum = 0 answer = 0 # 生成所有可能的 k^t 值 powers = set() LIMIT = 10**15 # 一个足够大的上限 if k == 1: powers.add(1) elif k == -1: powers.add(1) powers.add(-1) elif k == 0: powers.add(0) powers.add(1) else: power = 1 while abs(power) <= LIMIT: powers.add(power) # 防止不必要的计算,如果k的绝对值很大,power增长很快,集合很小 # 如果k的绝对值很小(但>=2),power会缓慢增长,但绝对值<=LIMIT时次数也不多 power *= k # 主循环 for num in arr: current_prefix_sum += num for power_val in powers: target = current_prefix_sum - power_val # 如果target在哈希表中,则累加其出现次数 if target in prefix_count: answer += prefix_count[target] # 将当前前缀和加入哈希表 prefix_count[current_prefix_sum] += 1 print(answer) if __name__ == "__main__": solve()

5.1 测试用例与验证

我们来用几个典型的测试用例验证一下算法的正确性。

用例1:简单情况

输入: 3 2 1 1 1 输出: 3

解释:数组[1, 1, 1]k=2。2的幂有1, 2, 4, 8...。子数组和:

  • [1]= 1 (是2^0)
  • [1, 1]= 2 (是2^1)
  • [1, 1, 1]= 3 (不是2的幂)
  • 其他单个元素或两个连续元素的和都不是2的幂。 所以答案是2?等等,还有[1]出现了三次(三个位置),所以是3。我们的算法会正确计算。

用例2:包含负数

输入: 5 -1 -1 -1 -1 -1 -1 输出: 6

解释:k=-1,幂为1-1。数组全是-1

  • 和为-1的子数组:每个单个元素都是,有5个。
  • 和为1的子数组:需要两个-1相加?-1 + -1 = -2,不对。实际上,长度为2的子数组和是-2,不是1。我们找和为1的。 观察前缀和:[-1, -2, -3, -4, -5]。 我们需要prefix[r] - prefix[l-1] = 1。 即prefix[l-1] = prefix[r] - 1。 例如,r=1时,prefix[1]=-1,需要prefix[l-1] = -2,但l-1最小为0 (prefix[0]=0),没有-2。 经过仔细计算(或运行程序),符合条件的子数组是:[-1, -1]? 不对,和是-2。实际上,只有当子数组包含偶数个-1时,和才可能是1-1。但偶数个-1的和是偶数(如-2, -4...),不会是奇数1。所以只有和为-1的子数组,即每个单个元素。答案是5?让我们再仔细想想k=-1时,幂可以是1-1。我们需要子数组和等于1-1。 单个元素-1符合(和等于-1)。 有没有子数组和等于1?比如从第2个到第3个元素:-1 + -1 = -2,不是。实际上,在这个全-1的数组中,任何子数组的和都是负的(因为每个元素都是负的),所以不可能有和为1的正数子数组。因此,只有5个和为-1的子数组。 但官方输出是6。我可能漏算了什么。让我们手动枚举长度为1到5的所有子数组: 长度1: 5个,和都是-1。 长度2: 4个,和都是-2。 长度3: 3个,和都是-3。 长度4: 2个,和都是-4。 长度5: 1个,和是-5。 只有5个啊。等等,题目中k=-1(-1)^0 = 1(-1)^1 = -1(-1)^2 = 1(-1)^3 = -1... 所以合法的幂是1-1。我们确实只找到了5个和为-1的。但答案是6。难道空子数组也算?或者0也被认为是(-1)^t对于某个t?不,(-1)^t永远是±1,不会是0。 我怀疑这个测试用例是我记错了,或者是其他数组。一个典型的能输出6的用例是[1, -1, 1, -1, 1]k=-1。我们换一个。

用例3:经典用例

输入: 4 2 2 2 2 2 输出: 8

解释:数组[2,2,2,2]k=2。2的幂有1,2,4,8...。 子数组和:

  • 长度1:[2](是2^1),有4个。
  • 长度2:[2,2]和=4 (是2^2),有3个。
  • 长度3:[2,2,2]和=6 (不是2的幂)。
  • 长度4:[2,2,2,2]和=8 (是2^3),有1个。 总数为 4+3+0+1 = 8。我们的算法应该能算出8。

通过自己构造一些小规模数组(n<=10),并打印出程序计算的answer以及所有符合条件的子数组,可以很好地验证算法的正确性。

6. 性能分析与优化技巧

6.1 时间复杂度与空间复杂度

  • 时间复杂度O(n * m),其中n是数组长度,m是可能的k^t的数量。对于|k| > 1mO(log(limit)),大约在60以内。对于k = 1, -1, 0m是常数(1或2)。因此,整体复杂度可以认为是O(n)O(n log(limit)),对于n=10^5完全足够。
  • 空间复杂度O(n + m)。哈希表在最坏情况下需要存储n个不同的前缀和(如果所有前缀和都不同),所以是O(n)powers集合大小m很小,可以忽略。

6.2 潜在性能瓶颈与优化

  1. 哈希表操作:对于每个位置i和每个power,我们都要进行一次哈希表查询prefix_count.get(target, 0)。在Python中,字典的get操作平均是O(1),但常数较大。如果m较大(比如k的绝对值很小,如k=2power增长慢,集合可能包含几十个值),内层循环的几十次查询累积起来可能成为瓶颈。一个微优化是使用target in prefix_count判断后再累加,避免两次查找(get本身包含查找)。但实测差异不大。

  2. 幂次集合的生成:对于|k|>1power增长很快,集合很小。但对于|k|=1的特殊情况,我们直接使用固定集合,避免了循环。这是必要的优化。

  3. 输入输出:在Python中,对于大数据量输入,使用sys.stdin.read()一次性读取所有数据然后分割,比反复调用input()要快得多。这在Codeforces这类竞赛平台上尤为重要。

  4. 使用C++的进一步优化:在C++中,可以使用unordered_map,但需要注意其哈希冲突在极端数据下可能导致性能退化到O(n)。一个更稳定的选择是使用map(基于红黑树,O(log n)操作),虽然理论复杂度高一点,但通常足够快且稳定。此外,C++中可以将powers存储为vector,并预先计算好大小。

6.3 算法思维扩展

这道题的解法核心是“前缀和 + 哈希表”,这个组合拳可以解决一大类“子数组和问题”。例如:

  • 和为K的子数组个数:这就是本题k=Kpowers只有{K}的情况。LeetCode上有一道经典题就是这样的。
  • 和可被K整除的子数组个数:此时需要用到前缀和模K的余数,公式变为(prefix[r] - prefix[l-1]) % K == 0,即prefix[r] % K == prefix[l-1] % K。同样可以用哈希表统计余数出现的次数。
  • 和为K的最长子数组长度:哈希表记录每个前缀和第一次出现的位置,然后寻找target = current_prefix_sum - K

掌握这个变换公式prefix[r] - prefix[l-1] = target以及其移项形式prefix[l-1] = prefix[r] - target,是解决这类问题的万能钥匙。

7. 常见错误与调试心得

在实现和调试这道题时,我以及我见过的新手常犯以下几个错误:

  1. 忘记初始化prefix_count[0] = 1:这是最常见的错误。没有这个初始化,所有从数组开头开始的子数组(即l=1的子数组)都会被漏掉。表现就是样例能过,但提交后某些测试点答案偏小。

  2. k=1, -1, 0的特殊情况处理不当

    • 对于k=1k=-1,使用通用的while循环生成powers会导致死循环。
    • 对于k=0,漏掉power=0的情况(只考虑了0^0=1,没考虑0^1, 0^2... = 0)。或者错误地认为0^0是未定义的。在竞赛题目的上下文中,通常约定0^0 = 1
  3. 整数溢出:主要在C++/Java中。在计算power *= k时,即使powerk都是long long,乘法结果也可能溢出。需要在乘法前判断:if (abs(power) > LIMIT / abs(k)) break;

  4. 答案溢出:答案answer可能非常大(最大可达n*(n+1)/2,约5e9),需要用64位整型(C++:long long, Python:int自动支持)。

  5. 枚举power的上界设置不当LIMIT设置得太小,可能会漏掉一些合法的、但值很大的k^t。设置得太大,对于|k|=1的情况会导致死循环(如果没特判)。一个安全的方法是:对于|k|>1,循环直到abs(power) > 10^14(因为前缀和绝对值最大约10^14);对于|k|<=1的特殊情况,单独处理。

  6. 错误理解子数组定义:子数组必须是连续的。我们的前缀和方法天然保证了连续性,因为prefix[l-1]prefix[r]对应的是原数组中连续的一段。

调试时,最好的方法是构造小数据,打印出中间变量。例如,打印出powers集合,看是否包含了所有你认为应该包含的值。对于每个位置i,打印出current_prefix_sum、枚举的power、计算出的target以及从哈希表中查到的次数。然后手动验证这些次数是否正确。对于边界情况(如全零数组、k=0k=1),单独测试。

最后,这道题“Molly‘s Chemicals”是一个绝佳的前缀和与哈希表练习题。它不像一些模板题那样直接套公式,而是需要你真正理解前缀和公式的变形,并灵活运用哈希表进行优化。搞懂它,你就能举一反三,解决一大片类似的子数组统计问题。在竞赛或面试中遇到,你就能从容地说:“哦,这个可以用前缀和+哈希表,时间复杂度O(n)。”

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 8:59:24

从求最大值实验深入理解C++模板:泛型编程与参数多态性

1. 从“求最大值”到理解C模板的威力 最近在辅导一些同学做C实验时&#xff0c;发现一个挺有意思的现象。实验题目是“设计一个求三个数中最大值的函数模板”&#xff0c;很多同学的第一反应是&#xff1a;这太简单了&#xff0c;不就是写个 max 函数&#xff0c;然后用 if-…

作者头像 李华
网站建设 2026/8/23 8:56:37

Allreduce算法:大模型分布式训练的核心通信原理与工程实践

1. 项目概述&#xff1a;为什么Allreduce是大模型训练的“生命线”&#xff1f;如果你最近关注过大模型相关的新闻或者技术讨论&#xff0c;大概率会看到“千亿参数”、“万亿token训练”这样的字眼。这些数字背后&#xff0c;是海量的计算和通信开销。一个直观的问题是&#x…

作者头像 李华
网站建设 2026/8/23 8:53:01

Go service如何搭建自己的可观测性?

那天凌晨两点&#xff0c;支付服务又开始报超时了。我打开 Kibana&#xff0c;熟练地输入 "error"&#xff0c;回车。三千条结果。再输入 "timeout"&#xff0c;一千二。然后我开始了那场熟悉的“猜谜游戏”&#xff1a;翻日志、对时间戳、开另一个窗口看监…

作者头像 李华
网站建设 2026/8/23 8:52:33

STM32 SD 卡 + FatFS 实战:掉电丢数据?f_sync 和簇对齐写救你

给温控器加数据记录功能那次&#xff0c;客户要求"断电前至少保留最近 1000 条记录"。我一开始用片上 Flash 轮流擦两页存&#xff0c;每条 16 字节&#xff0c;两页一共只能存 128 条。后来换了 SD 卡&#xff0c;FatFS 一挂&#xff0c;f_open 一个 CSV 文件一行行…

作者头像 李华
网站建设 2026/8/23 8:48:21

C++模板编程:从泛型思维到智能指针实现

1. 从“硬编码”到“泛型思维”&#xff1a;为什么我们需要C模板&#xff1f; 如果你写过一些C代码&#xff0c;尤其是处理过不同类型数据但逻辑几乎相同的函数&#xff0c;你大概率经历过这种痛苦&#xff1a;为了处理 int 和 double 两种类型的数据&#xff0c;你不得不写…

作者头像 李华