需要学会的前置技能:
快速幂
逆元
一定的数学推导能力
求 ∑ 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)
3
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)。
#include
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