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

💡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;
}
相关推荐
caimouse28 分钟前
ReactOS 窗口系统分析(5):可见区域与窗口 DC — vis.c + windc.c
c语言·开发语言
happyprince1 小时前
01-整体观-Codex全貌解读(源码)
算法·ai编程
俊基科技2 小时前
别自己写算法了,让这颗37.5毫米的模组替你“闭嘴干活”
神经网络·算法·硬件开发·ai降噪·语音模块·回音消除
_Narcissus_2 小时前
动态规划初探(含完整代码实现)
c语言·数据结构·c++·算法·动态规划·背包问题·最短路径
Cerman2 小时前
QEMU启动OVMF shell超时分析
c语言·windows·经验分享
世人万千丶3 小时前
鸿蒙项目实战 - 社区活动编排板:标签云布局算法与自动换行
学习·算法·华为·harmonyos·鸿蒙
zander2583 小时前
LeetCode 279. 完全平方数
算法·深度优先
法哥20123 小时前
用 C++ 控制 CMW100,到底在干什么?
开发语言·c++
典典分享指南4 小时前
VS Code Git 工作树:解锁多分支并行开发新体验
算法·决策树·逻辑回归·启发式算法