写在前面
需要学会的前置技能:
快速幂
逆元
一定的数学推导能力
原题题面
求 的结果。( )
input
3
output
22
题面分析
直接暴力计算肯定超时.
因为有许多的
是相同的,所以我们考虑从
入手。
为了后续工作方便,我们枚举
,使
转化为
------①
如何证明这步转化正确呢?
对于
,有
对于
,有
。。。
对于
,有
因此如果我们反过来枚举
的话可以发现,
某个
出现的前提就是
,①得证。
还有一个问题,为什么要反过来枚举
呢?
因为原来的角度入手的话,
是考虑
是
的因子,
但变成枚举
之后,
就变成考虑
是
的倍数。在计算上会很方便。
同时,
的指数
在
变化时是相对不变的,便于统计。
有了①后,我们设
,可以得到此时的
。
那对于每一段
,我们有
(统计
的总和)
因此我们可以得到
=
这样一来,复杂度就从
变为
了,然后把上式的
用逆元表示,
用快速幂求解即可。
然后…
然后…
就莫名其妙地超时了…
为什么呢?是哪里还可以简化运算吗?
答案是肯定的。
注意到上述式子中的
,它的取值其实是
所以其实这个部分可以利用数组去模拟(
),一开始数组里每个值都是
,每次更新
个(因为只用到
个),让
,这样就可以等效替代快速幂。
总复杂度大约为
。
AC代码(805ms)
#include<bits/stdc++.h>
using namespace std;
long long a[3000010];
const long long mod=1e9+7;
long long div2=500000004;//2对于1e9+7的逆元
void init1(int k) {
for(int i = 1; i <= k; i++) {
a[i] = 1;
}
}
void init2(int k) {
for(int i = 1; i <= k; i++) {
a[i] = (a[i] * i) % mod;
}
}
long long f(long long n)
{
long long sum=0;
init1(n);
for(long long j=1;j<=n;j++)
{
init2(n / j);
for(long long k=1;k<=n/j;k++)
{
long long l=min(n,(k+1)*j-1);//每一块的上界
sum=(sum+((((l-k*j+1+mod)%mod)*(k*j+l)%mod)*div2%mod)*a[k]%mod)%mod;
//数组a用来代替原来的快速幂
}
}
return sum%mod;
}
int main()
{
int n;
cin >> n;
cout << f(n);
return 0;
}
后记
感谢强大的wl大佬想出了用数组代替快速幂的神奇操作。
其实这个式子还可以化简,把
用错位相减法算出公式求解,但很麻烦。
DrGilbert 2019.10.12