【洛谷】前缀和入门

前缀和

引入——数组

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

数组都是从 0 开始编号的,但你是否下意识地想过,为什么数组要从 0 开始编号,而不是从 1 开始呢?

计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,它会首先通过下面的寻址公式,计算出该元素存储的内存地址:

a[i]_address = base_address + i * data_type_size

引入——数组求和

// 局部代码
int sum = 0;
int l, r; // 数组求和下标索引范围
int arr[N]; // 此处略去了对数组元素的赋值
for(int i = l; i <= r; ++i)
{
	sum += arr[i];
}

上面是进行一次求和,如果要多次进行求和?

int t;
cin >> t;
int arr[N];
int n;
cin >> n;
for(int i = 1; i <= n; ++i)
{
    cin >> arr[i];
}
while(t--)
{
	int l, r;
	cin >> l >> r;
    int sum = 0;
    for(int i = l; i <= r; ++i)
	{
		sum += arr[i];
	}
}

引入——时间复杂度

简单的来讲,可以理解为代码运行的次数…

第二段的时间复杂度,最坏情况为O(tn + n)。如果 t 很大,比如1e8 ,而 n 也很大,也为 1e8。那么,这段代码的运行次数在 1e16的情况。

假如现在有一台机子,一秒最多运行 1e8次,那么以当前这种情况就需要运行 1e8 s。显然时间消耗太长了需要改进…

引入——前缀和

前缀和就是从第一个元素开始到当前下标 i所有元素的和。我们还以代码来演示。

在这里插入图片描述

int t;
cin >> t;
int arr[N],sumn[N];
int n;
cin >> n;
for(int i = 1; i <= n; ++i)
{
    cin >> arr[i];
    sumn[i] = sumn[i - 1] + arr[i];
} 

while(t--)
{
	int l, r;
	cin >> l >> r;
    cout << sumn[r] - sumn[l - 1];
}

通过这一段修改,代码的复杂度就降到了O(n + t),这样如果还是刚才那个数据的话,只需要2s。

例题

[蓝桥杯 2022 省 A] 求和

P8772 蓝桥杯 2022 省 A 求和

使用前缀和之前
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 3;
int arr[N], n;

int main()
{
    // cin >> n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        // cin >> arr[i];
        scanf("%d", &arr[i]);
    }
    int sum = 0;
    for(int i = 1; i <=n; ++i)
    {
        for(int j = i + 1; j <= n; ++j)
        {
            sum += arr[i] * arr[j];
        }
    }
    // cout << sum;
    printf("%d\n", sum);
    return 0;
}

测试结果

在这里插入图片描述

使用前缀和之后
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    int n;
    scanf("%d", &n);
    vector<int> nums(n);
    for (register int i(0); i < n; ++i)
    {
        scanf("%d", &nums[i]);
    }
    vector<ll> temp(n);
    temp[0] = nums[0];
    for (register int i(1); i < n; ++i)
    {
        temp[i] = temp[i - 1] + nums[i];
    }
    ll res = 0;
    for (register int i(0); i < n - 1; ++i)
    {
        res += nums[i] * (temp[n - 1] - temp[i]);
    }
    printf("%lld\n",res);
    return 0;
}

测试结果

在这里插入图片描述

对比图

在这里插入图片描述

B3612 【深进1.例1】求区间和

B3612 【深进1.例1】求区间和

使用前缀和之前
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3;

int n, m, arr[N];

int main()
{
    cin >> n;
    // scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        cin >> arr[i];
        // scanf("%d", &arr[i]);
    }
    cin >> m;
    for(int i = 1; i <= m; ++i)
    {
        int l, r;
        cin >> l >> r;
        int res = 0;
        for(int i = l; i <= r; ++i)
        {
            res += arr[i];
        }
        cout << res << "\n";
        // printf("%d\n", res);
    }
}

运行结果

在这里插入图片描述

使用前缀和之后
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3;

int n, m, arr[N], sumn[N];

int main()
{
    cin >> n;
    // scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        cin >> arr[i];
        // scanf("%d", &arr[i]);
        sumn[i] = sumn[i - 1] + arr[i];
    }
    cin >> m;
    for(int i = 1; i <= m; ++i)
    {
        int l, r;
        cin >> l >> r;
        cout << sumn[r] - sumn[l - 1] << "\n";
        // printf("%d\n", sumn[r] - sumn[l - 1]);
    }
}

运行结果

在这里插入图片描述

对比图

在这里插入图片描述

P2697 宝石串

P2697 宝石串
思路:将绿球视为 -1,红球视为 1,计算这个区间和。如果某一段区间的和为 0,那么这一段的宝石就是稳定的宝石串,从中找出最长的即可。

题目代码(这段代码写麻烦了,建议跳过这个看下一个,这个太丑陋了)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 3;
typedef pair<int, int> PII;
string str;
int sumn[N];
  
map<int, PII> mp;

int main()
{
    cin >> str;
    int n = str.length();
    for (int i = 1; i <= n; ++i)
    {
        if (str[i - 1] == 'G')
            sumn[i] = sumn[i - 1] - 1;
        else
            sumn[i] = sumn[i - 1] + 1;
    }
    for (int i = 1; i <= n; ++i)
    {
        if (sumn[i] == 0)
            mp[0] = {i, i};
        else
        {
            if(mp.find(sumn[i]) != mp.end())
            {
                int f = mp[sumn[i]].first;
                mp[sumn[i]] = {f, i};
            }
            else{
                mp[sumn[i]] = {i, i};
            }
        }
    }
    int res = 0;
    for(auto x : mp)
    {
        if(x.first == 0)
        {
            res = max(res, x.second.first);
        }
        else
        {
            res = max(res, x.second.second - x.second.first);
        }
    }
    cout << res << "\n";
}

简洁点的代码,C语言选手也能很好看懂的

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 3;
string str;
int sumn[N], record[N];

int main()
{
    cin >> str; // 对题目输入的读取
    int n = str.length();
    int minn = INT_MAX, res = 0, maxn = INT_MIN;
    for (int i = 1; i <= n; ++i)
    {
        if (str[i - 1] == 'G')
            sumn[i] = sumn[i - 1] - 1;
        else
            sumn[i] = sumn[i - 1] + 1;
        minn = min(minn, sumn[i]);
    }
    for (int i = 1; i <= n; ++i)
        if (sumn[i] == 0)
            res = max(res, i);
        else
            if (record[sumn[i] + minn] == 0)
                record[sumn[i] + minn] = i;
            else
                res = max(res, i - record[sumn[i] + minn]);
    printf("%d", res);
}

进阶——二维前缀和

刚才是一维数组的处理情况,那么如果是二维数组该如何处理呢?
二维数组的前缀和是为了方便我们计算范围从 x1 行到 x2 行,y1列到 y2 列这个范围内矩阵元素的和,比如下方这个二维数组:

在这里插入图片描述

那么对于上面的二维数组,它的二维前缀和数组该怎么去求呢?
i行第j列的二维前缀和,在原本第 i行第j列值的基础上,还需要加上在它之前已经处理好的第 i-1行第j列的前缀和 与 第 i行第j - 1列的前缀和,并且因为这两个前缀和都同时覆盖了 第i-1行 与 第 j-1列的前缀和,所以需要减去一份这一块的前缀和。表达式如下:

// sumn数组是二维前缀和数组,arr数组是原数组
sumn[i][j] = arr[i][j] + sumn[i-1][j] + sumn[i][j - 1] - sumn[i - 1][j - 1];

在这里插入图片描述
有了前缀和数组之后,我们如何获得一个子矩阵范围内的和呢?大家是否还记得小时候学的计算一个矩阵内的矩阵面积。已知橘黄色区域面积、蓝色区域面积、黄色区域面积、粉色区域面积,如何去计算红色区域面积?很明显,橘黄色区域面积 - 蓝色区域面积 - 黄色区域面积 + 粉色区域面积(因为粉色区域被蓝色和黄色同时覆盖),二维前缀和同理。
前缀和示例

二维前缀和部分代码

int sumn[N][N], t;

int n, m;
cin >> n >> m;
for(int i = 1; i <= n; ++i)
{
	for(int j = 1; j <= m; ++j)
	{
		cin >> t;
		sumn[i][j] = sumn[i - 1][j] + sumn[i][j - 1] + t - sumn[i - 1][j - 1];
	}
}
// 从r1,c1,r2,c2区域的所有原数组之和,其中r1 < r2,c1 < c2
sumn[r2][c2] - sumn[r2][c1 - 1] - sumn[r1 - 1][c2] + sumn[r1 - 1][c1 - 1];

细心的大佬应该发现了,在我上方代码中未出现之前提到的原数组——arr数组。在我们日常处理二位前缀和的时候,如果原数组在之后的处理中没有任何作用了,可以只声明一个二维前缀和数组。如果出题人限制了空间大小,而且数据也比较大的时候,可以通过这个办法减少多余的空间。

P1387 最大正方形

P1387 最大正方形

求出所给数组的前缀和,若区域和等于区域面积,则说明这是一个符合题意的结果

#include <bits/stdc++.h>
using namespace std;

const int N = 103;
int n, m, arr[N][N], sum[N][N], res = 1, i, j;

int main()
{
    scanf("%d%d", &n, &m);
    for (i = 1; i <= n; ++i)
    {
        for (j = 1; j <= m; ++j)
        {
            scanf("%d", &arr[i][j]);
            sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + arr[i][j];
        }
    }
    int temp = 2;
    while (temp <= min(n, m))
    {
        for (i = temp; i <= n; ++i)
        {
            for (j = temp; j <= m; ++j)
            {
                if(sum[i][j] - sum[i-temp][j] - sum[i][j - temp] + sum[i - temp][j - temp] == temp * temp)
                {
                    res = max(res, temp);
                }
            }
        }
        ++temp;
    }
    printf("%d\n", res);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值