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

💡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,定义f[i]为下标从0到i的数字的和,这就是前缀和数组,我们可以在一开始读取数据的时候就能初始化出来,即f[i] = f[i-1] + num

那么区间y的和不就是f[z] - f[x - 1]的值吗

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

实操代码

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 最大子段和 - 洛谷

算法原理

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

由于我们要计算最大的区间的和,我们可以这样思考,区间和无非就是f[y] - f[x]的和的最大值

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

并且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. 二维前缀和

题目链接:

【模板】二维前缀和

算法原理

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

f[x][y]表示的是前x行,前y列的所有数的和

那么我们如何推导f[x][y]?

我们会发现f[x][y]要用到f[x][y-1]和f[x-1][y]中的值,同时f[x][y-1]和f[x-1][y]重合了f[x][y]

因此f[x][y] = f[x][y-1] + f[x-1][y] - f[x-1][y-1] + v[x][y]

处理了f[x][y]的值,那么如何计算(a,b)到(c,d)这之间的值?

同样的道理

ret = f[c][d] - f[c][b] - f[a][d] + f[a][b]

实操代码

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;
}
相关推荐
啊吧怪不啊吧1 小时前
算法王冠上的明珠——动态规划之路径问题(第一篇)
大数据·算法·贪心算法·动态规划
承渊政道1 小时前
C++学习之旅【C++类和对象(中)】
c语言·c++·visual studio
崇山峻岭之间1 小时前
C++ Prime Plus 学习笔记037
c++·笔记·学习
木心爱编程1 小时前
Qt C++ 插件开发指南:插件架构设计与动态加载实战
开发语言·c++·qt
Henry Zhu1231 小时前
23种设计模式介绍以及C语言实现
c语言·开发语言·设计模式
青铜发条1 小时前
【算法】常见校验算法对比
算法·信息与通信·校验
LinHenrY12271 小时前
初识C语言(数据在内存中的存储)
c语言·开发语言·算法
R-G-B1 小时前
BM53 缺失的第一个正整数,哈希表,原地哈希(扩展思路)
算法·哈希算法·哈希表·原地哈希
CC.GG1 小时前
【C++】STL容器----map和set的使用
开发语言·c++