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时,arr和sum各需要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,满足:
- 存在整数 q(商),使得
被除数 = 除数 × q + r; 0 ≤ r < 除数。
对于 (5 - 10) = -5,除数是 10000019,我们要找这样的 r:
-5 = 10000019 × q + r,且0 ≤ r < 10000019。- 解得 q = -1(因为 10000019 × (-1) = -10000019),代入得:
-5 = (-10000019) + r→r = -5 + 10000019 = 10000014。 - 这个 r=10000014 才是数学意义上的 "正确模结果",因为它满足 "非负且小于除数"。
二、编程语言中 % 运算符的实际行为
C++、Java、Python 等语言的 % 运算符,其实是 "求余运算",不是严格的 "数学模运算",结果符号由 被除数 决定:
- 若被除数为正,结果为正(比如
7%3=1,10%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); - 若用
abs,abs(-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 = 10000014,10000014 % 10000019 = 10000014)。
这个公式的核心是:通过加 MOD 把负数 "拉回" 非负区间,再取模确保结果小于 MOD,完美匹配数学模运算的要求。
总结
- 题目要的是 "数学模结果"(非负、小于除数);
- C++ 的
%是 "求余运算",结果可能为负; abs只能解决 "符号",解决不了 "结果必须小于除数",所以错误;- 正确做法是
(差值 + MOD) % MOD,兼顾 "非负" 和 "小于除数" 两个条件。
