本题题意是对每对数字求乘积,然后对所有乘积求和。
容易想到用前缀和去处理,用第i个数乘以【第i+1之后所有数的和】,对每个i这样操作,得到的数再求和,就是最终的答案。
但问题在于,数字可能很大,需要取模。
【模运算的性质】
模运算与基本四则运算有些相似,但是除法例外。其规则如下:
-
(a + b) % p = (a % p + b % p) % p (1)
-
(a - b) % p = (a % p - b % p) % p (2)【注意:两个取模的结果进行相减操作时(易知前面的数理论上应该大,但取模之后可能小于后面的数)此时应该加上MOD或其倍数,再对相减结果进行取模,从而保证输出为正】
-
(a * b) % p = (a % p * b % p) % p (3)
-
a ^ b % p = ((a % p)^b) % p (4)
最初的错误代码是:
#include<stdio.h>
typedef long long ll;
ll a[200005];
ll num[200005];
const int mod = 1000000007;
int n;
int main(){
scanf("%d",&n);
ll ans = 0;
for(int i = 1;i <= n;i++){
scanf("%lld",&num[i]);
a[i] = (a[i-1] + num[i])%mod;
}
for(int i = 1;i <= n-1;i++){
ans = (ans + num[i]*(a[n]-a[i]))%mod;
}
printf("%lld\n",ans);
}
错误原因:没有考虑相减可能负数;取余不充分。
首先应该注意的是:
对于
a[i] = (a[i-1] + num[i])%mod;
而言,这样求出来的前缀和就不再是原来意义上的前缀和,因为取模之后,后面的数可能比前面的数小,这时候正确的做法是在下面再次加mod.
所以下面要写成:
ans = (ans + num[i]*(a[n]-a[i]+mod))%mod;
这样就能把可能是负数的a[n] - a[i]变成“正数”,且最终结果不变。
当然,为了保险起见,还要写成
ans = (ans + ( (num[i]%mod) * ( (a[n]-a[i]+mod)%mod ) )%mod;
最终AC的代码也就变成了:
#include<stdio.h>
typedef long long ll;
ll a[200005];
ll num[200005];
const int mod = 1000000007;
int n;
int main(){
scanf("%d",&n);
ll ans = 0;
for(int i = 1;i <= n;i++){
scanf("%lld",&num[i]);
a[i] = (a[i-1] + num[i])%mod;
}
for(int i = 1;i <= n-1;i++){
ans = (ans + ((num[i]%mod)*((a[n]-a[i]+mod)%mod))%mod)%mod;
/*用到的性质:
(a*b) % p = (a%p * b%p)%p,每一次这个小部分都满足性质相等
所有数相加后取余,等于【当前加数】加上前面所有数求和取余的结果,然后再最终取余。这样不会超出long long范围,而且结果正确
*/
}
printf("%lld\n",ans);
}
当然,最正常的思路应当是:
#include<stdio.h>
typedef long long ll;
ll a[200005];
ll num[200005];
const int mod = 1000000007;
int n;
int main(){
scanf("%d",&n);
ll ans = 0;
for(int i = 1;i <= n;i++){
scanf("%lld",&num[i]);
a[i] = a[i-1] + num[i];//实际上long long最多是10^14,可以存储,无需取模
}
for(int i = 1;i <= n-1;i++){
ans = (ans + ( ((num[i])%mod) * (((a[n]-a[i]))%mod) ) %mod ) %mod;
/*用到的性质:
(a*b) % p = (a%p * b%p)%p,每一次这个小部分都满足性质相等
所有数相加后取余,等于【当前加数】加上前面所有数求和取余的结果,然后再最终取余。这样不会超出long long范围,而且结果正确
*/
}
printf("%lld\n",ans);
}