study in pta + 豆包(求区间和)(前缀和算法)(如何处理zhan栈溢出和超出时间复杂度问题)(2025.12.2)

7-8 求区间和 - 实验8 新生赛赛前测试(25级3、4班补题) (pintia.cn)

1.我先给出一个我开始写的代码,大家看看能看出几个错误

cpp 复制代码
// 前缀和
// 第a到b号元素(含)的和取模后的结果。

#include <iostream>
using namespace std;

#define int long long

signed main(){
    int n;
    cin>>n;
    int arr[n+10]={0};
    int sum[n+10]={0};
    for(int i=0;i<n;i++){
        cin>>arr[i];
        //cout<<arr[i];
    }

    for(int i=0;i<n;i++){
        for(int j=0;j<=i;j++){
            sum[i]+=arr[j];
        }
        // cout<<sum[i]<<endl;
    }

    
    int k;
    cin>>k;
    int a,b;
    for(int i=0;i<k;i++){
        cin>>a>>b;
        a--;
        b--;
        int mmax=max(a,b);
        int mmin=min(a,b);
        if(i>0)cout<<endl;
        cout<<abs((sum[mmax]-sum[mmin-1])%10000019);
       // int result=(sum[mmax]-sum[mmin-1])%10000019;
        //printf("%d",result);
    }
    return 0;
}

2.揭晓谜底

一共是两个致命错误和两个细节潜在错误(负数,小标)

致命错误就是我标题的两个问题

(1)先说一个栈溢出

变长数组(VLA)导致的栈溢出(最致命,大数据量必崩)

C++ 标准 不支持 int arr[n+10]int sum[n+10] 这种 "变长数组"(数组大小由变量 n 决定)。

  • 数组存储在 栈空间 中,而栈空间通常只有几 MB(比如 Windows 默认栈大小 1MB~8MB);
  • n=1e6 时,arrsum 各需要 1e6 * 8字节(long long)= 8MB,两个数组共 16MB,远超栈空间限制,会直接导致 栈溢出崩溃

(2)另一个是运行超时

前缀和计算方式错误(时间超限的根本原因)

你的前缀和计算用了 双重循环for(int i=0;i<n;i++) 嵌套 for(int j=0;j<=i;j++)),时间复杂度是 O(N²)。而题目中 N 最大可达 1e6,O (N²) 会执行 1e12 次操作,远远超过时间限制(600ms 最多支持~1e8 次操作),直接导致超时。

正确的前缀和计算应该是 O(N) 线性时间:sum[i] = sum[i-1] + arr[i](sum [i] 表示前 i 个元素的和),无需嵌套循环。

怎么判断的可以自学一下时间复杂度的判断

修改完如下

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

#define int long long
const int maxn = 1e6 + 10;
int arr[maxn]={0};
int sum[maxn]={0};

signed main(){
    int n;
    cin>>n;
    // int arr[n+10]={0};
    // int sum[n+10]={0};
    for(int i=0;i<n;i++){
        cin>>arr[i];
        //cout<<arr[i];
    }

    for(int i=0;i<n;i++){
        
            sum[i]=sum[i-1]+arr[i];
        
        // cout<<sum[i]<<endl;
    }

    
    int k;
    cin>>k;
    int a,b;
    for(int i=0;i<k;i++){
        cin>>a>>b;
        a--;
        b--;
        int mmax=max(a,b);
        int mmin=min(a,b);
        if(i>0)cout<<endl;
        cout<<abs((sum[mmax]-sum[mmin-1])%10000019);
       // int result=(sum[mmax]-sum[mmin-1])%10000019;
        //printf("%d",result);
    }
    return 0;
}

再来是细节错误

(1)先说一个简单的,就是下标最好和题目的编号对应(如果可以),不要用0下标

(2)第二个是负数取模问题

未处理取模后的负数情况(结果错误)

题目要求输出 "对 10000019 取模的结果",而 (sum[mmax] - sum[mmin-1]) 可能为负数(比如前缀和未提前取模时,sum [mmax] < sum [mmin-1];或提前取模后,sum [mmax] 因取模变小)。你的代码用 abs() 处理,但这是错误的!因为取模的负数结果需要通过 "加模再取模" 修正,而非直接取绝对值。例如:(5 - 10) % 10000019 = -5,正确的取模结果是 10000014(-5 + 10000019),但 abs(-5) = 5,完全不符合要求。

这时候可能有同学会问:为什么正确取模结果是这个

简单回答就是

对负数取模数学上和&计算机不一样

详细回答就是

要搞懂这个问题,核心是理解 "数学中的模运算" 和 "编程语言中的取模运算符(%)" 的区别------ 前者要求结果非负,后者的结果符号由被除数决定。咱们一步步拆解:

一、先明确:数学中的 "模"(余数)定义

数学里的模运算,核心是找到一个 非负整数 r,满足:

  1. 存在整数 q(商),使得 被除数 = 除数 × q + r
  2. 0 ≤ r < 除数

对于 (5 - 10) = -5,除数是 10000019,我们要找这样的 r:

  • -5 = 10000019 × q + r,且 0 ≤ r < 10000019
  • 解得 q = -1(因为 10000019 × (-1) = -10000019),代入得:-5 = (-10000019) + rr = -5 + 10000019 = 10000014
  • 这个 r=10000014 才是数学意义上的 "正确模结果",因为它满足 "非负且小于除数"。

二、编程语言中 % 运算符的实际行为

C++、Java、Python 等语言的 % 运算符,其实是 "求余运算",不是严格的 "数学模运算",结果符号由 被除数 决定:

  • 若被除数为正,结果为正(比如 7%3=110%4=2);
  • 若被除数为负,结果为负(比如 -7%3=-1-5%10000019=-5)。

所以你的代码中 (sum[b] - sum[a-1])%MOD 得到 -5,是 C++ 中 % 运算符的正常行为,但这个结果不符合题目要求(题目要的是数学意义上的非负模结果)。

三、为什么 abs(-5) 是错误的?

abs(-5) 会直接取绝对值得到 5,但 5 不满足 "数学模运算" 的逻辑:

  • 比如:(5 - 10000024) = -10000019-10000019 % 10000019 = 0(数学上正确结果是 0);
  • 若用 absabs(-10000019%10000019) = abs(0) = 0(看似正确);
  • 但再看:(5 - 10000020) = -10000015-10000015 % 10000019 = -10000015
  • abs 得到 10000015,但数学上正确结果是 10000019 - 10000015 = 4(因为 -10000015 + 10000019 = 4),此时 abs 就完全错了!

本质原因:abs 只是单纯 "翻转为正数",没有考虑 "结果必须小于除数" 这个核心条件。只有当被除数的绝对值小于除数时(比如 -5 < 10000019),abs 看似 "能用",但一旦被除数绝对值大于等于除数,就会出错。

四、正确的修正方法:(a - b + MOD) % MOD

无论 (a - b) 是正还是负,加上 MOD 再取模,都能得到数学意义上的非负结果:

  • (a - b) 为正:(正数 + MOD) % MOD 仍等于原正数(比如 (15 + 10000019) % 10000019 = 15);
  • (a - b) 为负:(负数 + MOD) 会变成 "正数且小于 MOD",再取模就是正确结果(比如 -5 + 10000019 = 1000001410000014 % 10000019 = 10000014)。

这个公式的核心是:通过加 MOD 把负数 "拉回" 非负区间,再取模确保结果小于 MOD,完美匹配数学模运算的要求。

总结

  • 题目要的是 "数学模结果"(非负、小于除数);
  • C++ 的 % 是 "求余运算",结果可能为负;
  • abs 只能解决 "符号",解决不了 "结果必须小于除数",所以错误;
  • 正确做法是 (差值 + MOD) % MOD,兼顾 "非负" 和 "小于除数" 两个条件。
相关推荐
明朝百晓生35 分钟前
强化学习[page13]【chapter7】时序差分方法算法介绍
算法
youngee1137 分钟前
hot100-40将有序数组转换为二叉搜索树
数据结构·算法
子枫秋月38 分钟前
排序算法原理与实现详解
数据结构·算法·排序算法
红队it39 分钟前
【Spark+Hive】基于Spark大数据旅游景点数据分析可视化推荐系统(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅
大数据·python·算法·数据分析·spark·django·echarts
BestOrNothing_201540 分钟前
【C++基础】Day 6:前置++ VS 后置++(语法底层 + STL规范 + 面试高频)
c++·运算符重载·面试八股·前置++·后置++·stl迭代器
缘三水44 分钟前
【C语言】10.操作符详解(下)
c语言·开发语言·c++·语法·基础定义
渡我白衣44 分钟前
深入理解算法库的灵魂——彻底掌握 <algorithm> 的范式、迭代器约束、隐藏陷阱与性能真相
数据结构·c++·人工智能·网络协议·mysql·rpc·dubbo
CoovallyAIHub1 小时前
为什么企业如今不应该忽视计算机视觉?计算机视觉如何为企业降本增效、规避风险?
深度学习·算法·计算机视觉
报错小能手1 小时前
C++流类库 文件流操作
开发语言·c++