问题
Bob有一个无限数列a,其中ai=3 * i * i +1,他会对你询问q次,每次给你一个区间[l , r],让你求区间[l , r] 的各项之和,这个数字可能非常大,所以你需要对1e9+7取模。
1 <= q <= 1e5 , 1 <= l <= r <= 1e18。
输入
第一行输入一个整数 q,代表 q 次询问。
接下来q行,每行输入两个整数 l , r 代表询问的区间。第一行输入一个整数 q,代表 q 次询问。
接下来 q 行,每行输入两个整数 l , r 代表询问的区间。
输出
输出 q 行,每行一个整数表示当前询问的答案。
算法思想
这道题乍一看很想用暴力或者线段树什么解决,但是暴力的话时间肯定会超出,线段树的话,没有给定的范围,而且范围太大,也不现实,再仔细想想,其实有一个公式可以应用,从1到n的平方和等于 n * (n + 1) * (2*n + 1) / 6。这样子好像就可以解决问题了,但是数据太大了就要考虑数据类型的范围,由于 l 和 r 的数据有可能很大,如果再平方相加,没有一个数据类型可以存放该数据,所以要利用取模运算的性质。取模运算与基本四则运算有些相似,但是除法例外。
其规则如下:
(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
a ^ b % p = ((a % p)^b) % p
但不满足 (a / b)% p = (a % p) / b,
也不满足 (a / b)% p = (a % p) / b % p。
由于不满足除法,除以六的运算在与 3 消去后仍还有除以 2,我们要想办法将除以 2 转换为乘法的运算,使得(a / b)% p = (a * c)% p,此时就需要用到乘法逆元,关于乘法逆元比较实用的有两条定理,一条是费马小定理,还有一个是扩展欧几里得。
费马小定理
此处略去其证明,因为博主自己也不会, 仅仅是对其进行应用。因为在算法竞赛中模数p总是质数,所以可以利用费马小定理 ,bp−1 % p = 1,由于 b * bp-2= bp-1,(a / b)%p =((a / b)%p * bp-1 %p)%p =(a * bp-2)%p 。所以,bp-1是 b 的乘法逆元 。又由于数据的大小和算法的时间问题,此题不能直接使用pow函数,而是要用快速幂乘来解决。
代码实现
# include<bits/stdc++.h>
# define m 1000000007
using namespace std;
typedef long long ll;
/*快速幂乘的函数*/
ll ksmc(ll a,ll b){
ll result=1;
while(b>0){
if(b&1)
result=(result*a)%m;
b>>=1;
a=((a%m)*(a%m))%m;
}
return result%m;
}
int main()
{
ll q,l,r,sum1,sum2,a,b;
cin>>q;
while(q--)
{
cin>>l>>r;
sum1=(r-l+1)%m;
l--;
//将除以二转换为乘以2的逆元2的m-2次方,前面利用公式就行
a=(r%m*((r+1)%m)%m*(r%m*2+1))%m*ksmc(2,m-2)%m;
b=(l%m*((l+1)%m)%m*(l%m*2+1))%m*ksmc(2,m-2)%m;
sum2=(a-b+m)%m;
sum1=(sum1+sum2)%m;
cout<<sum1<<'\n';
}
return 0;
}
注意事项
这有一点注意事项,在计算 a 和 b 的值时,千万不要加太多的括号,第一个是因为加的括号太多会导致理不太清,还有更重要的一点是,可能会导致代码错误。本人就是这样过来的。
举例子说明,
a=(r%m*((r+1)%m)%m*(r%m*2+1))%m*ksmc(2,m-2)%m;
a=((((r%m)*((r+1)%m)*((2*r%m+1)%m))%m)*ksmc(2,m-2)%m)%m;
这两句乍一看好像没啥区别,只是第二条语句多加了很多括号有点令人对不上,但是前一种写法ac了,后一种却是wa。为什么呢?因为后一条语句是对r,r+1,2*r+1这些语句取模,但是假设这些数据都恰巧比取模的m值小一点点,三个值相乘就已经足够超出数据类型的范围了。而前一者则是每次相乘后对结果进行取模,这样则能保证得到的数据不会超出范围。记录一下这个坑。