蓝桥杯基础--前缀和

目录

[1. 前缀和原理与特点](#1. 前缀和原理与特点)

[2. 实现前缀和](#2. 实现前缀和)

[3. 例题](#3. 例题)

3.1区间次方和

3.2小郑的蓝桥平衡串


在算法竞赛(如蓝桥杯)中,如果在处理海量数据时遇到了频繁查询"某一段区间数据总和"的需求,如果每次查询都用 for 循环从头加到尾,程序肯定会因为超时(TLE)而痛失分数。

为了解决这个问题,算法竞赛中引入了一个极其优雅且高效的技巧------前缀和(Prefix Sum)。今天我们就来深入拆解前缀和的原理,并结合两道蓝桥杯真题进行实战演练。

1. 前缀和原理与特点

前缀和(prefix)顾名思义,就是从数组开头一直累加到当前位置的"前面所有元素的总和"。

简单来说,前缀和数组是由用户输入的基础数组生成的一个辅助数组。假设我们有一个原数组 a,那么它的前缀和数组 prefix 中,第 i 个元素 prefix[i] 就代表了原数组中 a[1]a[i] 的所有元素之和。

前缀和最大的特点与核心价值在于:用空间换时间。 通过在读取数据时额外花一点点时间(线性时间)预处理出一个前缀和数组,我们在后续面对成千上万次"求某个区间总和"的查询时,就可以把查询时间瞬间从线性级别降维打击到常数级别(也就是瞬间得出结果)。

2. 实现前缀和

实现前缀和通常分为两步:预处理和区间查询。

为了防止在计算 prefix[i - 1] 时出现数组下标越界(比如下标变成 -1),我们在算法竞赛中通常习惯将数组下标从 1 开始使用 ,并且默认 prefix[0] = 0

预处理构建前缀和数组:

复制代码
for(int i = 1; i <= n; i++)
    prefix[i] = prefix[i - 1] + a[i];

在这个循环中,当前位置的前缀和,就等于"前一个位置的前缀和"加上"当前位置的元素值"。这种递推方式非常高效。

求区间和:

假设我们要查询原数组中从第 L 个元素到第 R 个元素(包含 L 和 R)的总和,我们只需要用一个简单的减法公式:

复制代码
sum(L, R) = prefix[R] - prefix[L - 1];

3. 例题

下面我们结合两道蓝桥杯真题,来看看前缀和算法在实战中是如何大显身手的。

3.1区间次方和

https://www.lanqiao.cn/problems/3382/learning/?page=1&first_category_id=1&problem_id=3382

思路解析: 这道题不仅要求我们求区间和,还要求我们求区间的"k次方"和(k的值在1到5之间)。同时,因为结果可能非常大,题目要求对 10的9次方加7 取模。

既然 k 只有 1 到 5 这五种可能,我们完全可以定义一个二维的前缀和数组 prefix[6][N],分别存储 1 次方、2 次方、一直到 5 次方的前缀和。

另外,代码中包含两个非常关键的细节:

  1. 快速求幂(pow_k函数):利用位运算的思想快速求出 base 的 exp 次方并取模,效率远超普通的 for 循环相乘。

  2. 处理取模时的负数 :在 C++ 中,两个正数取模后的结果相减,可能会产生负数(比如 3 - 5 = -2)。为了保证结果恒为正且符合取模规则,区间求和的公式要写成 (prefix[k][r] - prefix[k][l - 1] + p) % p,也就是先加上模数 p 再取模。

参考代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll p = 1e9 + 7;
const int N = 1e5 + 10;
int a[N];
ll prefix[6][N];

// 自定义求幂函数:计算 (base^exp) % p
ll pow_k(ll base, int exp) {
    ll res = 1;
    base = base % p; // 防止底数过大(虽然本题 a[i] <= 100 不会越界,但加上更严谨)
    while (exp > 0) {
        if (exp % 2 == 1) res = (res * base) % p; // 如果指数当前位是1,累乘到结果中
        base = (base * base) % p;                 // 底数平方
        exp /= 2;                                 // 指数右移
    }
    return res;
}

int main()
{
  ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  int n, m;
  cin >> n >> m;
  for(int i = 1; i <= n; i++) cin >> a[i];
  // 前缀和数组
  for(int k = 1; k <= 5; k++)
  {
    prefix[k][0] = 0;
    for(int i = 1; i <= n; i++)
    {
      // 前i个的和 = 前i-1个的和 + 当前元素的k次方,取模
      prefix[k][i] = (prefix[k][i - 1] + pow_k(a[i], k)) % p;
    }
  }

  // 处理每个查询
  while(m--)
  {
    int l, r, k;
    cin >> l >> r >> k;
    // 求区间和
    ll ans = (prefix[k][r] - prefix[k][l - 1] + p) % p;
    cout << ans << '\n';
  }
  return 0;
}

3.2小郑的蓝桥平衡串

https://www.lanqiao.cn/problems/3419/learning/?page=1&first_category_id=1&problem_id=3419

思路解析: 题目要求寻找最长的平衡子串,所谓平衡串,就是字符串中两种特定字符的数量相等。 这里用到了一种非常经典的"前缀和变体"技巧:把其中一种字符(例如 L)看作 +1,把其他字符看作 -1。

这样一来,如果某一个区间 [i+1, j] 是平衡串,就意味着这个区间里的 +1 和 -1 数量相等,也就是这个区间的和为 0 。 根据前缀和公式:区间和 = prefix[j] - prefix[i]。 既然区间和等于 0,那么就推导出 prefix[j] == prefix[i]

因此,这道题的本质就变成了:在 prefix 数组中,寻找两个值相同且距离最远的下标 i 和 j。代码中通过嵌套的两层循环遍历所有的起点和终点,找出了跨度最大的那对相等的前缀和。

参考代码:

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char s[N];
int prefix[N];

int main()
{
  cin >> s + 1;
  int len = strlen(s + 1);
  for(int i = 1; i <= len; i++) prefix[i] = prefix[i - 1] + (s[i] == 'L' ? 1 : -1); 
  
  int ans = 0;
  for(int i = 0; i <= len; i++)
  {
    for(int j = i + 1; j <= len; j++)
    {
      if(prefix[i] == prefix[j])
      {
        ans = max(ans, j - i);
      }
    }
  }
  cout << ans << '\n';
  return 0;
}

本章完。

相关推荐
华农DrLai3 小时前
什么是知识图谱?实体、关系、属性分别是什么?
人工智能·算法·llm·nlp·prompt·知识图谱
chh5633 小时前
从零开始学习C++ -- 基础知识
开发语言·c++·windows·学习·算法
Lzh编程小栈3 小时前
【数据结构与算法】C语言实现双向链表 (Double Linked List) 全解析
c语言·开发语言·数据结构·链表
AlenTech3 小时前
142. 环形链表 II - 力扣(LeetCode)
数据结构·leetcode·链表
剑心诀3 小时前
【C语言 数据结构】易错题集
c语言·数据结构·算法
我能坚持多久3 小时前
【初阶数据结构12】——C语言实现八大排序算法与代码深度解析
c语言·数据结构·排序算法
旺仔.2913 小时前
顺序容器:list双向链表 详解
数据结构·c++·链表·list
楼田莉子3 小时前
CMake学习:CMake在静态库工程场景上应用
开发语言·c++·后端·学习·软件构建
森G3 小时前
28、视图基类 QAbstractItemView---------Model/View模型视图
c++·qt