《算法竞赛从入门到国奖》算法基础:入门篇-前缀和

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

前言

[1. 一维前缀和](#1. 一维前缀和)

算法原理

实操代码

[2. 最大字段和](#2. 最大字段和)

算法原理

实操代码

[3. 二维前缀和](#3. 二维前缀和)

算法原理

实操代码

[4. 激光炸弹](#4. 激光炸弹)

算法原理

实操代码


前言

前缀和的核心思想是预处理,可以在暴力枚举的过程中快速查询到结果,相当于是对枚举的优化,是经典的用空间换时间的做法

1. 一维前缀和

题目链接:

【模板】前缀和

算法原理

暴力求解:

很简单,我们每次询问直接无脑从l遍历到r不就得了,不就是O(n*m),差不多10^10次方吗(

(pass)

前缀和:

由于是一个连续区间的和,注意是连续区间,我们可以这样思考

区间y的和是不是区间z的和 - 区间x的和?并且区间z和区间x的和都是从原点开始的

因此,我么可以定义一个数组f,定义fi为下标从0到i的数字的和,这就是前缀和数组,我们可以在一开始读取数据的时候就能初始化出来,即fi = fi-1 + num

那么区间y的和不就是fz - fx - 1的值吗

至于为什么是fx - 1而不是fx,因为题目给出的是闭区间,即x,y,那么fx就包含vx这个数字,如果减fx就减去了vx

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int n,m;cin>>n>>m;
    vector<long long> f(n+1,0);
    for(int i = 1;i<=n;i++)
    {
        long long num;cin>>num;
        f[i] = f[i-1] +num;//预处理前缀和数组
    }
    while(m--)
    {
        int a,b;cin>>a>>b;
        cout<<f[b] - f[a-1]<<endl;
    }
    return 0;
}

2. 最大字段和

题目链接:

P1115 最大子段和 - 洛谷

算法原理

跟上题很相似的是,同样是一个字段,即中间的一个区间的和,那么也可以用前缀和解决

由于我们要计算最大的区间的和,我们可以这样思考,区间和无非就是fy - fx的和的最大值

我们对于前缀和数组从头开始枚举,这样fy就确定了,那么只需要找出最小的fx即可

并且x 一定是小于y的

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    int n;cin >> n;
    vector<long long> f(n+1);
    for(int i = 1; i <= n; i++)
    {
        long long x; cin >> x;
        f[i] = f[i - 1] + x;
    }
    long long ret = -1e20;
    long long prevmin = 0;//最小的前缀和区间
    for(int i = 1; i <= n; i++)
    {
        ret = max(ret, f[i] - prevmin);
        prevmin = min(prevmin, f[i]);//更新最小的前缀和区间
    }
    cout << ret << endl;
    return 0;
}

3. 二维前缀和

题目链接:

【模板】二维前缀和

算法原理

一维前缀和fn是前n个数字的和,那么我们推广到二位前缀和数组可以这样定义

fxy表示的是前x行,前y列的所有数的和

那么我们如何推导fxy?

我们会发现fxy要用到fxy-1和fx-1y中的值,同时fxy-1和fx-1y重合了fxy

因此fxy = fxy-1 + fx-1y - fx-1y-1 + vxy

处理了fxy的值,那么如何计算(a,b)到(c,d)这之间的值?

同样的道理

ret = fcd - fcb - fad + fab

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main()
{
    
    int a,b,c;cin>>a>>b>>c;
    vector<vector<long long>> f(a+1,vector<long long>(b+1,0));
    for(int i = 1;i<=a;i++)
    {
        for(int j = 1;j<=b;j++)
        {
            int num;cin>>num;
            f[i][j] = f[i][j-1] + f[i-1][j] + num - f[i-1][j-1];
        }
    }
    while(c--)
    {
        int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;
        cout<<f[x2][y2] - f[x1-1][y2] - f[x2][y1-1] + f[x1-1][y1-1]<<endl;
    }
    return 0;
}

4. 激光炸弹

P2280 HNOI2003 激光炸弹 - 洛谷

算法原理

由于获得的价值是一个m为边长的正方形,我们假设往(x,y)这个位置扔炸弹,那么这不就是(x- m,y - m)到(x,y)的和吗?因此我们使用二位前缀和数组,以m * m的方格一行一列地枚举即可

当然如果m非常大,比整个数组的行列还要大,那么就越界访问了,因此我们得处理这种情况

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int MAX = 5005;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<vector<int>> v(MAX, vector<int>(MAX, 0));
    for (int i = 0; i < n; i++) {
        int x, y, value;
        cin >> x >> y >> value;
        v[x + 1][y + 1] += value;
    }
    for (int i = 1; i < MAX; i++) {
        for (int j = 1; j < MAX; j++) {
            v[i][j] = v[i][j] + v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1];//二维前缀和
        }
    }
    
    int ret = 0;
    int r = min(m, MAX - 1);//处理m>MAX的情况
    for (int i = r; i < MAX; i++) {
        for (int j = r; j < MAX; j++) {
            int sum = v[i][j] - v[i - r][j] - v[i][j - r] + v[i - r][j - r];
            if (sum > ret) {
                ret = sum;
            }
        }
    }
    
    cout << ret;
    return 0;
}
相关推荐
不想写代码的星星27 分钟前
从分支预测角度看 C++:为什么你的热循环慢得离谱?
c++
过期动态32 分钟前
【LeetCode 热题 100】接雨水
java·数据结构·算法·leetcode·职场和发展
春日见33 分钟前
5分钟入门强化学习之动态规划算法与实现
大数据·人工智能·python·算法·机器学习·计算机视觉
一抹晴空35 分钟前
Keil MDK AC6 compiler编译报错,与AC5区别
c语言·arm开发·单片机
郝学胜-神的一滴44 分钟前
Qt 高级开发 018:复刻经典登录界面布局与窗口美化全解析
开发语言·c++·qt·程序人生·用户界面
scx_link1 小时前
线性回归的总结:
算法·机器学习·线性回归
郝亚军1 小时前
IEEE 754 单精度浮点的SEM表示
开发语言·c++·算法
青山师1 小时前
动态规划算法深度解析:从状态转移方程到工业级优化
数据结构·算法·面试·动态规划·代理模式·java面试
黎阳之光1 小时前
数智透明·安全兜底|黎阳之光透明矿山,AI+数字孪生守护矿山生命线
人工智能·物联网·算法·安全·数字孪生
吴可可1231 小时前
控制弦高精度的样条离散化方法
算法