牛客练习赛53 B 美味果冻(数学推导+快速幂优化)

写在前面

需要学会的前置技能:
快速幂
逆元
一定的数学推导能力

原题题面

∑ i = 1 n ∑ j = 1 i i ⌊ \sum_{i=1}^{n}\sum_{j=1}^{i}i\lfloor i=1nj=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 n3e6)

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=1nj=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=1ni=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 ij,①得证。

还有一个问题,为什么要反过来枚举 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)j1,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)j1,n))(min((k+1)j1,n)kj+1)/2kj
(统计 k j k^j kj的总和)
因此我们可以得到
∑ j = 1 n ∑ i = j n i ⌊ \sum_{j=1}^{n}\sum_{i=j}^{n}i\lfloor j=1ni=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=1nk=1jn(kj+min((k+1)j1,n))(min((k+1)j1,n)kj+1)/2kj
这样一来,复杂度就从 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 n3e6),一开始数组里每个值都是 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
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 akj用错位相减法算出公式求解,但很麻烦。
DrGilbert 2019.10.12

你可能感兴趣的:(数论)