蓝桥杯练习题——前缀和

本文介绍了几个与C++编程相关的算法问题,包括计算最坏情况下壁画总和、使用前缀和解决查询区间和、子矩阵和、K倍区间计数、统计子矩阵和递增三元组,以及激光炸弹的前缀和优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.壁画

在这里插入图片描述

思路

1.求最坏情况下,画的墙总和是多少
2.画的墙在中间连续一段,画了的墙长度是 n / 2 向上取整
3.取最大的 n / 2 向上取整区间和

#include<iostream>
using namespace std;
const int N = 5e6 + 10;
char s[N];
int a[N];
int t, n;

int main(){
    cin>>t;
    for(int i = 1; i <= t; i++){
        cin>>n;
        // 从下标1开始读取
        cin>>s + 1;
        for(int i = 1; i <= n; i++){
            int x = s[i] - '0';
            a[i] = a[i - 1] + x;
        }
        // n / 2 向上取整
        int x = (n - 1) / 2 + 1;
        int res = 0;
        for(int i = x; i <= n; i++){
            res = max(res, a[i] - a[i - x]);
        }
        // Case #1: 6
        cout<<"Case #"<<i<<": "<<res<<endl;
    }
    return 0;
}

2.前缀和

在这里插入图片描述

思路

模板题

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
int n, m;

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        a[i] += a[i - 1];
    }
    int l, r;
    while(m--){
        scanf("%d%d", &l, &r);
        printf("%d\n", a[r] - a[l - 1]);
    }
    return 0;
}

3.子矩阵的和

在这里插入图片描述

思路

模板题

#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N][N];
int n, m, q;

int main(){
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
        }
    }
    int x1, y1, x2, y2;
    while(q--){
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", a[x2][y2] - a[x1 - 1][y2] - a[x2][y1 - 1] + a[x1 - 1][y1 - 1]);
    }
    return 0;
}

4.K倍区间

在这里插入图片描述

思路

1.求有多少个 (a[r] - a[l - 1]) % k = 0,转化成 a[r] % k = a[l - 1] % k,即有多少个 l 和 r 匹配
2.用哈希表 cnt[x] 存储余数为 x 有多少个
3.右边界 a[r] 取 0 也是 k 的倍数,所以 cnt[0] 要初始化为 1,作为左边界
在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
long long a[N];
int cnt[N];
int n, k;

int main(){
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        a[i] += a[i - 1];
    }
    long long res = 0;
    cnt[0] = 1;
    for(int i = 1; i <= n; i++){
    	// 第一次是找到左端点,下次找到余数相同的右端点再累加
        res += cnt[a[i] % k];
        cnt[a[i] % k]++;
    }
    printf("%lld", res);
    return 0;
}

5.统计子矩阵

在这里插入图片描述

思路

1.由于每个数都是大于等于 0,保证了单调性,数越多总和越大
2.右边界往右走,左边界也一定往右走
3.暴力枚举上下边界,双指针枚举左右边界
4.方案数最多有 C(500, 2) * C(500, 2),要开 long long

#include<iostream>
using namespace std;
const int N = 510;
int a[N][N];
int n, m, k;

int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
        }
    }
    // i, l   i, r
    // j, l   j, r 
    long long res = 0;
    for(int i = 1; i <= n; i++){
        for(int j = i; j <= n; j++){
            for(int l = 1, r = 1; r <= m; r++){
                while(l <= r && a[j][r] - a[j][l - 1] - a[i - 1][r] + a[i - 1][l - 1] > k) l++;
                if(l <= r) res += r - l + 1;
            }
        }
    }
    printf("%lld", res);
    return 0;
}

6.递增三元组

在这里插入图片描述

思路

1.枚举 B,对于每个 bj 有多少个 ai 小于 bj,有多少个 ck 大于 bj
2.cnt[i] 为 i 在 a 中出现了多少次,s[i] 为 在 a 中 0 ~ i 一共出现了多少次

#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N], c[N];
int cnt1[N], cnt2[N], s1[N], s2[N];
int n;

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
        cnt1[a[i]]++;
    }
    for(int i = 1; i <= n; i++) scanf("%d", &b[i]);
    for(int i = 1; i <= n; i++){
        scanf("%d", &c[i]);
        cnt2[c[i]]++;
    }
    for(int i = 0; i <= 100000; i++){
        s1[i] = s1[i - 1] + cnt1[i];
        s2[i] = s2[i - 1] + cnt2[i];
    }
    long long res = 0;
    // 枚举 B 数组
    for(int i = 1; i <= n; i++){
        res += 1ll * s1[b[i] - 1] * (s2[100000] - s2[b[i]]);
    }
    printf("%lld", res);
    return 0;
}

7.激光炸弹

在这里插入图片描述

思路

1.枚举每个 r * r 区域的前缀和,求最大值
2.题目下标从 0 开始,所以我们要加 1,防止出现 -1 下标

#include<iostream>
using namespace std;
const int N = 5e3 + 10;
int a[N][N];
int n, r;

int main(){
    scanf("%d%d", &n, &r);
    r = min(r, 5001);
    int x, y, w;
    while(n--){
        scanf("%d%d%d", &x, &y, &w);
        x++, y++;
        a[x][y] += w;
    }
    for(int i = 1; i <= 5001; i++){
        for(int j = 1; j <= 5001; j++){
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
        }
    }
    long long res = 0;
    for(int i = r; i <= 5001; i++){
        for(int j = r; j <= 5001; j++){
            res = max(res, 1ll * a[i][j] - a[i - r][j] - a[i][j - r] + a[i - r][j - r]);
        }
    }
    printf("%lld", res);
    return 0;
}
### 蓝桥杯前缀和算法题解法 #### 一维前缀和的应用 在一维场景中,前缀和的核心思想是通过预先计算数组的部分和来加速后续的区间查询操作。对于给定的一个长度为N的一维数组`a[]`,可以通过构建一个新的数组`f[]`来进行预处理[^5]。具体而言,公式定义如下: ```cpp for (int i = 1; i <= N; ++i) { f[i] = f[i - 1] + a[i]; } ``` 这样做的好处在于,当我们需要频繁查询任意区间 `[l, r]` 的和时,只需执行一次简单的减法运算即可得到结果 `sum(l, r) = f[r] - f[l-1]`。 #### 示例代码 以下是基于上述原理的一段 C++ 实现代码: ```cpp #include <iostream> using namespace std; const int MAX_N = 1e5 + 7; long long a[MAX_N], f[MAX_N]; void preprocess(int n) { for (int i = 1; i <= n; ++i) { f[i] = f[i - 1] + a[i]; // 构建前缀和数组 } } // 查询 [l, r] 区间内的和 long long query_sum(int l, int r) { return f[r] - f[l - 1]; } int main() { int n, q; cin >> n; for (int i = 1; i <= n; ++i) { cin >> a[i]; } preprocess(n); cin >> q; while (q--) { int l, r; cin >> l >> r; cout << query_sum(l, r) << endl; } return 0; } ``` 这段程序展示了如何利用前缀和优化多次区间求和的操作效率。 #### 二维前缀和扩展 除了适用于一维数据结构外,前缀和还可以推广至二维情况,用于高效地计算矩形区域中的元素总和[^2]。假设有一个大小为 M×N 的矩阵 A,则其对应的前缀和矩阵 S 可按以下方式初始化: ```cpp S[x][y] = A[x][y] + S[x-1][y] + S[x][y-1] - S[x-1][y-1]; ``` 当询问左上角位于 `(x1,y1)` 和右下角处于 `(x2,y2)` 所包围范围之内的数值累积量时,可依据下面表达式迅速获取答案: ```cpp result = S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1-1][y1-1]; ``` 这种方法同样能够显著降低时间开销,使得原本 O(K^2) 复杂度的任务降至常数级别完成。 #### 综合应用实例分析 考虑到实际竞赛环境中可能遇到更复杂的混合型问题,例如结合差分、二分查找甚至高级的数据结构如线段树等工具共同解决问题的情况。此时,合理运用前缀和作为辅助手段往往能起到事半功倍的效果[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值