写在前面
需要学会的前置技能:
快速幂
逆元
一定的数学推导能力
原题题面
求 ∑ i = 1 n ∑ j = 1 i i ⌊ \sum_{i=1}^{n}\sum_{j=1}^{i}i\lfloor ∑i=1n∑j=1ii⌊ i j \frac{i}{j} ji ⌋ j m o d ( 1 e 9 + 7 ) \rfloor^{j}\ mod\ (1e9+7) ⌋j mod (1e9+7)的结果。( n ≤ 3 e 6 n\leq3e6 n≤3e6)
input
3
output
22
题面分析
直接暴力计算肯定超时.
因为有许多的
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
\rfloor
⌋是相同的,所以我们考虑从
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
\rfloor
⌋入手。
为了后续工作方便,我们枚举
j
j
j,使
∑
i
=
1
n
∑
j
=
1
i
i
⌊
\sum_{i=1}^{n}\sum_{j=1}^{i}i\lfloor
∑i=1n∑j=1ii⌊
i
j
\frac{i}{j}
ji
⌋
j
\rfloor^{j}
⌋j
转化为
∑
j
=
1
n
∑
i
=
j
n
i
⌊
\sum_{j=1}^{n}\sum_{i=j}^{n}i\lfloor
∑j=1n∑i=jni⌊
i
j
\frac{i}{j}
ji
⌋
j
\rfloor^{j}
⌋j------①
如何证明这步转化正确呢?
对于
i
=
1
i=1
i=1,有
j
=
1
j=1
j=1
对于
i
=
2
i=2
i=2,有
j
=
1
,
2
j=1,2
j=1,2
。。。
对于
i
=
n
i=n
i=n,有
j
=
1
,
2
,
.
.
.
,
n
j=1,2,...,n
j=1,2,...,n
因此如果我们反过来枚举
j
j
j的话可以发现,
某个
i
i
i出现的前提就是
i
≥
j
i\geq j
i≥j,①得证。
还有一个问题,为什么要反过来枚举
j
j
j呢?
因为原来的角度入手的话,
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
\rfloor
⌋是考虑
j
j
j是
i
i
i的因子,
但变成枚举
j
j
j之后,
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
\rfloor
⌋就变成考虑
i
i
i是
j
j
j的倍数。在计算上会很方便。
同时,
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
j
\rfloor^j
⌋j的指数
j
j
j在
i
i
i变化时是相对不变的,便于统计。
有了①后,我们设
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
=
k
\rfloor=k
⌋=k,可以得到此时的
i
∈
[
k
j
,
m
i
n
(
(
k
+
1
)
j
−
1
,
n
)
]
i \in [kj,min((k+1)j-1,n)]
i∈[kj,min((k+1)j−1,n)]。
那对于每一段
⌊
\lfloor
⌊
i
j
\frac{i}{j}
ji
⌋
=
k
\rfloor=k
⌋=k,我们有
(
k
j
+
m
i
n
(
(
k
+
1
)
j
−
1
,
n
)
)
∗
(
m
i
n
(
(
k
+
1
)
j
−
1
,
n
)
−
k
j
+
1
)
/
2
∗
k
j
(kj+min((k+1)j-1,n))*(min((k+1)j-1,n)-kj+1)/2*k^j
(kj+min((k+1)j−1,n))∗(min((k+1)j−1,n)−kj+1)/2∗kj
(统计
k
j
k^j
kj的总和)
因此我们可以得到
∑
j
=
1
n
∑
i
=
j
n
i
⌊
\sum_{j=1}^{n}\sum_{i=j}^{n}i\lfloor
∑j=1n∑i=jni⌊
i
j
\frac{i}{j}
ji
⌋
j
\rfloor^{j}
⌋j
=
∑
j
=
1
n
∑
k
=
1
⌊
n
j
⌋
(
k
j
+
m
i
n
(
(
k
+
1
)
j
−
1
,
n
)
)
∗
(
m
i
n
(
(
k
+
1
)
j
−
1
,
n
)
−
k
j
+
1
)
/
2
∗
k
j
\sum_{j=1}^{n}\sum_{k=1}^{\lfloor \frac{n}{j}\rfloor} (kj+min((k+1)j-1,n))*(min((k+1)j-1,n)-kj+1)/2*k^j
∑j=1n∑k=1⌊jn⌋(kj+min((k+1)j−1,n))∗(min((k+1)j−1,n)−kj+1)/2∗kj
这样一来,复杂度就从
O
(
n
2
)
O(n^2)
O(n2)变为
O
(
n
(
l
o
g
n
)
2
)
O(n(logn)^2)
O(n(logn)2)了,然后把上式的
/
2
/2
/2用逆元表示,
k
j
k^j
kj用快速幂求解即可。
然后…
然后…
就莫名其妙地超时了…
为什么呢?是哪里还可以简化运算吗?
答案是肯定的。
注意到上述式子中的
k
j
k^j
kj,它的取值其实是
1
1
,
2
1
,
3
1
.
.
.
,
1
2
,
2
2
,
3
2
,
.
.
.
1^1,2^1,3^1...,1^2,2^2,3^2,...
11,21,31...,12,22,32,...
所以其实这个部分可以利用数组去模拟(
n
≤
3
e
6
n\leq3e6
n≤3e6),一开始数组里每个值都是
1
1
1,每次更新
n
/
j
n/j
n/j个(因为只用到
n
/
j
n/j
n/j个),让
a
[
i
]
∗
=
i
a[i]*=i
a[i]∗=i,这样就可以等效替代快速幂。
总复杂度大约为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
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大佬想出了用数组代替快速幂的神奇操作。
其实这个式子还可以化简,把
a
∗
k
j
a*k^j
a∗kj用错位相减法算出公式求解,但很麻烦。
DrGilbert 2019.10.12