Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >枚举+优化(7)——前缀和1

枚举+优化(7)——前缀和1

作者头像
mathor
发布于 2018-06-19 08:10:06
发布于 2018-06-19 08:10:06
59800
代码可运行
举报
文章被收录于专栏:mathormathor
运行总次数:0
代码可运行
例1

 给定一个长度为N的数组:A1,A2,...,AN。(N <= 100000,1 <= A[i] <= 100000)。然后有M个询问,每次询问给两个整数L,R问A[L]~A[R]的和是多少。(M <= 100000)。  这道题最直接的做法就是每次询问的时候,用一个循环累加A[L]~A[R]的和,伪代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ask(L,R)
Sum = 0
For i = L...R
    Sum += A[i]
return Sum

 上面这段伪代码,处理一个询问的时间复杂度是O(R-L),考虑到R最大是N,L最小是1,所以可以认为是O(N)。而通过前缀和,我们可以把每次询问的复杂度降到O(1)

思路

 假设我们有这么一个数组:A = [A1,A2,...,AN]。我们将A[i]加到A[j]的和称为部分和,记作S[i,j],上面的题目就是询问M次部分和。为了减少计算部分和的复杂度,我们先计算出前缀和:S[i] = A[1] + A[2] + ... + A[i]。前缀和可以通过递推的方法O(N)求出来

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
S[0] = 0
For i = 1...N
    S[i] = s[i-1] + A[i]

 我们看下图,第一排[1,2,5,4,3]是A数组,第二排[0,1,3,8,12,15]就是前缀和。当我们有了前缀和,想要计算S[i,j] = A[i] + A[i+1] + ... + A[j]的时候,直接用S[j] - S[i-1]就行了,比如我们要算A[2]+A[3]+A[4] = 2+5+4=11,只要算S[4]-S[1]=12-1=11即可

 所以我们利用前缀和就可以把计算部分和的复杂度降到O(1),只用计算一次减法。注意A数组是从下标1开始的,但是前缀和数组是从下标0开始的,并且S[0]的值0。这样可以在计算从A1开始的部分和时也不会出问题,例如A1+A2+A3=S[3]-S[0] = 8-0=8。

例2.K倍区间
思路1 暴力枚举
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ans = 0
For i = 1...N
    For j = i...N
        Sum = 0
        For p = i...j
            Sum += A[p]
            If Sum % k == 0
                Ans++

 这个算法的时间复杂度是O(N^3^)。

思路2 前缀和优化

 优化的思路就是先把部分和,转换成前缀和的差。题目要求A[i]+A[i+1]+…+A[j]是K的倍数,等价于S[j]-S[i-1]是K的倍数,等价于S[j]与S[i-1]模K余数相等。所以我们可以还是2重循环枚举i和j,但是直接用前缀和判断是不是K的倍数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Ans = 0
For i = 1...N
    For j = i...N
        If (S[j] - S[i - 1]) % k == 0
            Ans++

 优化之后的复杂度是O(N^2),还不足够优秀,还是会超时。我们再进一步分析一下要求的答案和前缀和的关系,看一下下面这张图:

 图中的第一排[1, 2, 5, 4, 3]是A数组。绿色的第二排是前缀和,天蓝色的第三排是前缀和%K之后的结果,这里我们假设K=2。  我们之前的算法,实际上就是在第三排的数组里,找两个相等的值(这里解释一下为什么是找相等的值,因为如果两个值相等,那么s[j] - s[i-1]=0,0%k=0)。每一对相等的值,都对应着一个K倍区间。比如第三排有3个0,第一个0和第二个0,对应[1, 2, 5]这个区间;第一个和第三个0,对应[1, 2, 5, 4]这个区间;第二个和第三个0,对应[4]这个区间。  所以我们已知第三排数组有3个0和3个1的情况下,可以直接用组合数求出来K倍区间的数目:C(3,2)+C(3, 2)=6。C(3, 2)是指从3个物品里取出2个来的组合数。因为有3个0和3个1,所以答案就是C(3, 2)+C(3, 2)。  把上面统计的思路归纳一下,就是:计算前缀和S[0], S[1], S[2], … S[N]。统计S[]中模K余0, 1, 2 … K-1的数量,记为cnt[0], cnt[1], cnt[2] … cnt[K-1]。答案就是:cnt[0]×(cnt[0]-1)/2 + cnt[1]×(cnt[1]-1)/2 +… +cnt[K-1]×(cnt[K-1]-1)/2

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <unordered_map>
using namespace std;
int n,k,a[100001],s[100001];
unordered_map<int,int> cnt;
//cnt[0]存储的是余数是0的前缀和有几个
//cnt[1]存储的是余数是1的前缀和有几个
//以此类推
int main()
{
    cin >> n >> k;
    for(int i = 1;i <= n;i++)
        cin >> a[i];
    s[0] = 0;
    cnt[0] = 1;
    for(int i = 1;i <= n;i++)
    {
        s[i] = (s[i-1] + a[i]) % k;
        cnt[s[i]]++;
        //一边求前缀和
        //一边统计前缀和模K的余数出现了多少次
    }
    long long ans = 0;
    //根据每个余数出现的次数用组合数求答案
    for(int i = 0;i < k;i++)
        ans += (long long)(cnt[i]) * (cnt[i] - 1) / 2;
    //把答案累加上C(cnt[i], 2)
    //也就是cnt[i]*(cnt[i]-1)/2
    cout << ans;
    return 0;
}

 上面的程序既用到了前缀和优化,也用了哈希表统计每个前缀和余数出现的次数。总复杂度是O(N+K)

例2 K的倍数

 我们要找到最长的K倍区间。实际上就是找到前缀和余数最左边和最右边的0的位置,算一下长度;再找到最左边和最右边的1的位置,算一下长度……最后找到最左边和最右边K-1的位置,算一下长度。最终取其中最长的区间就是答案。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <unordered_map>
using namespace std;
int n,k,a[100001],s[100001];
unodered_map<int.int>lmost.rmost;
int main()
{
    cin >> n;
    for(int i = 1;i <= n;i++)
        cin >> a[i];
    cin >> k;
    for(int i = 1;i <= n;i++)
    {
        lmost[i] = n + 1;
        rmost[i] = -1;
    }
    s[0] = 0;
    lmost[0] = rmost[0] = 0;
    for(int i = 1;i <= n;i++)
    {
        //一边计算前缀和
        //一边更新余数的最左位置和最右位置
        s[i] = (s[i-1] + a[i]) % k;
        if(lmost[s[i]] > i)
            lmost[s[i]] = i;
        if(rmost[s[i]] < i)
            rmost[s[i]] = i;
    }
    int ans = 0;
    //根据每个余数出现的次数用组合数求答案
    //前缀和都余i的对应的区间长度
    for(int i = 0;i < k;i++)
        ans = rmost[i] - lmost[i];
    cout << ans;
    return 0;
}

 我们用lmost和rmost保存第一个前缀和余数最左和最右的下标。整个程序复杂度也是O(n+k)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
枚举+优化(8)——前缀和2
例3.题目链接:hihoCoder1534  这道题要注意一下数据范围。首先N小于等于10万。其次Ai,也就是数组中每个数的值,是在负100万到正100万之间。假如这里Ai都是正整数的话,那么总
mathor
2018/06/12
6140
【优选算法篇】前缀之美,后缀之韵:于数列深处追寻算法的动与静
给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。
半截诗
2025/05/29
1100
【优选算法篇】前缀之美,后缀之韵:于数列深处追寻算法的动与静
我爱学算法之—— 前缀和(上)
通过上图,我们可以发现:我们要求的[l , r]区间的和s就等于区间[1 , r]的和 减去区间[1 , l]的和。
星辰与你
2025/06/08
640
我爱学算法之—— 前缀和(上)
前缀和、二维前缀和与差分的小总结
如果我给你一串长度为n的数列a1,a2,a3......an,再给出m个询问,每次询问给出L,R两个数,要求给出区间[L,R]里的数的和,你会怎么做,若是没有了解过前缀和的人看到这道题的想法可能是对于m次询问,我每次都遍历一遍它给的区间,计算出答案,这样子的方法固然没错,但是其时间复杂度达到了O(n*n),如果数据量稍微大一点就有可能超时,而我们如果使用前缀和的方法来做的话就能够将时间复杂度降到O(n+m),大大节省了运算时间。至于怎么用,请看下面一小段代码
ACM算法日常
2019/07/05
2.6K0
前缀和算法练习集
前六个测试点满足 1≤n≤10。 所有测试点满足 1≤n≤10^5,0−10000≤a_i≤1000。
timerring
2023/03/27
3930
前缀和算法练习集
【优选算法】Prefix-Kage:前缀和的算法影(下)
题解: 💻第一步: 要计算前后和相等的数的下标(不包括该数),显然要计算前后缀和
DARLING Zero two
2024/12/24
1070
【优选算法】Prefix-Kage:前缀和的算法影(下)
【C++】前缀和算法专题
⽤ dp[i] 表⽰: [1, i] 区间内所有元素的和,那么 dp[i - 1] ⾥⾯存的就是 [1,i - 1] 区间内所有元素的和,那么:可得递推公式: dp[i] = dp[i - 1] +arr[i] ;
啊QQQQQ
2024/11/19
1110
【C++】前缀和算法专题
程序员进阶之算法练习(四十七)
题目链接 题目大意: 给出一个整数1~n的排列。 接下来有m个询问,每个询问包括 l, r, x。 (l <= x <= r) [l, r]区间内的数字会进行一次从小到大的排序,然后得到一个新的1到n的排列,问第x个数字是否等于原来的第x个数字; 每次询问之后,数组会变回初始的排列顺序;
落影
2020/09/01
7210
深度解析算法之前缀和
这个题的话就是下面的样子,我们第一行输入 3 2的意思即是这个数组是3个元素大小的数组,2是接下来我们是需要输入两行数据下标的 然后第二行我们输入的n个整数表示的就是我们数组中的元素 然后后面的2行就是我们想计算数组中从哪里到哪里的和 这里的话我们是第一个数到第二个数的和,那么我们这里的就是3了
Undoom
2025/04/21
700
深度解析算法之前缀和
【西法带你学算法】一次搞定前缀和
我花了几天时间,从力扣中精选了五道相同思想的题目,来帮助大家解套,如果觉得文章对你有用,记得点赞分享,让我看到你的认可,有动力继续做下去。
lucifer210
2020/10/26
8550
【西法带你学算法】一次搞定前缀和
前缀和--详讲
T组数据,每组有N个数,然后给出R,L。目标是让你求出在区间[R,L]之间的和。(0<= R < L <=N)。
杨鹏伟
2020/09/11
3930
我爱学算法之—— 前缀和(中)
遍历到i位置时,判断该位置是否是中心下标,也就是该位置左侧所有元素是否等于右侧所有元素。
星辰与你
2025/06/08
520
我爱学算法之—— 前缀和(中)
【算法/学习】前缀和&&差分
差分数组的好处是可以简化运算,例如想要给一个区间 [l,r] 上的数组加一个常数c,原始的方法是依次加上c,这样的时间复杂度是O(n)的。但是如果采用差分数组的话,可以大大降低时间复杂度到O(1)。
IsLand1314
2024/10/15
1490
【算法/学习】前缀和&&差分
【算法刷题指南】前缀和
南桥
2024/12/14
1100
【算法】前缀和与差分
前缀和可以快速求出原数组里面一段数的和。比如求一段区间[l,r],如果按照原来的做法,需要循环一遍,O(n),有前缀和的算法:
平凡的人1
2023/10/15
3250
【算法】前缀和与差分
【刷题】前缀和入门
☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆ ☆ミヾ(∇≦((ヾ(≧∇≦)〃))≧∇)ノ彡☆
叫我龙翔
2024/04/25
920
【刷题】前缀和入门
枚举+优化(6)——双指针优化2
例3.题目链接:hihoCoder1514  先写一个暴力枚举的伪代码: ans = MAX_INT For i = 1...M { For j = 1...N {
mathor
2018/06/19
5080
【算法专题】前缀和
题目:给定一个长度为n的数组 a1​, a2​, …an. 接下来有q次查询, 每次查询有两个参数l, r. 对于每个询问, 请输出 al + al + 1 + … + ar
YoungMLet
2024/03/01
1660
【算法专题】前缀和
【算法一周目】从时光的边缘看世界:前缀和揭示的算法真谛
给定一个长度为 n 的整数数组 arr 和 q 个查询,每个查询由两个整数 l 和 r 组成,表示区间 [l, r]。请计算出每个区间内所有元素的和。
HZzzzzLu
2024/12/31
990
【算法一周目】从时光的边缘看世界:前缀和揭示的算法真谛
算法专题四: 前缀和
根据题意, 创建一个前缀和数组, dp[i] = dp[i -1] + arr[i], 再使用前缀和数组, 要求的区域ret = dp[r] - dp[l-1], 这里我们为什么要这样求dp[i]呢? 还要绕一大圈子, 直接相加不就行了 , 但是如果直接相加求还不如我们的暴力解法呢, 这里还要开辟空间, 但是我们使用dp[i]求解只需遍历一遍数组即可求出前缀和
用户11317877
2024/10/16
910
算法专题四: 前缀和
相关推荐
枚举+优化(8)——前缀和2
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验