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

写在前面

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

原题题面

i = 1 n j = 1 i i \sum_{i=1}^{n}\sum_{j=1}^{i}i\lfloor i j \frac{i}{j} j   m o d   ( 1 e 9 + 7 ) \rfloor^{j}\ mod\ (1e9+7) 的结果。( n 3 e 6 n\leq3e6 )

input

3

output

22

题面分析

直接暴力计算肯定超时.
因为有许多的 \lfloor i j \frac{i}{j} \rfloor 是相同的,所以我们考虑从 \lfloor i j \frac{i}{j} \rfloor 入手。
为了后续工作方便,我们枚举 j j ,使
i = 1 n j = 1 i i \sum_{i=1}^{n}\sum_{j=1}^{i}i\lfloor i j \frac{i}{j} j \rfloor^{j}
转化为
j = 1 n i = j n i \sum_{j=1}^{n}\sum_{i=j}^{n}i\lfloor i j \frac{i}{j} j \rfloor^{j} ------①
如何证明这步转化正确呢?
对于 i = 1 i=1 ,有 j = 1 j=1
对于 i = 2 i=2 ,有 j = 1 , 2 j=1,2
。。。
对于 i = n i=n ,有 j = 1 , 2 , . . . , n j=1,2,...,n
因此如果我们反过来枚举 j j 的话可以发现,
某个 i i 出现的前提就是 i j i\geq j ,①得证。

还有一个问题,为什么要反过来枚举 j j 呢?
因为原来的角度入手的话, \lfloor i j \frac{i}{j} \rfloor 是考虑 j j i i 的因子,
但变成枚举 j j 之后, \lfloor i j \frac{i}{j} \rfloor 就变成考虑 i i j j 的倍数。在计算上会很方便。
同时, \lfloor i j \frac{i}{j} j \rfloor^j 的指数 j j i i 变化时是相对不变的,便于统计。
有了①后,我们设 \lfloor i j \frac{i}{j} = k \rfloor=k ,可以得到此时的 i [ k j , m i n ( ( k + 1 ) j 1 , n ) ] i \in [kj,min((k+1)j-1,n)]

那对于每一段 \lfloor i j \frac{i}{j} = k \rfloor=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
(统计 k j k^j 的总和)
因此我们可以得到
j = 1 n i = j n i \sum_{j=1}^{n}\sum_{i=j}^{n}i\lfloor i j \frac{i}{j} j \rfloor^{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
这样一来,复杂度就从 O ( n 2 ) O(n^2) 变为 O ( n ( l o g n ) 2 ) O(n(logn)^2) 了,然后把上式的 / 2 /2 用逆元表示, k j k^j 用快速幂求解即可。

然后…
然后…
在这里插入图片描述
就莫名其妙地超时了…
为什么呢?是哪里还可以简化运算吗?
答案是肯定的。
注意到上述式子中的 k j k^j ,它的取值其实是 1 1 , 2 1 , 3 1 . . . , 1 2 , 2 2 , 3 2 , . . . 1^1,2^1,3^1...,1^2,2^2,3^2,...
所以其实这个部分可以利用数组去模拟( n 3 e 6 n\leq3e6 ),一开始数组里每个值都是 1 1 ,每次更新 n / j n/j 个(因为只用到 n / j n/j 个),让 a [ i ] = i a[i]*=i ,这样就可以等效替代快速幂。
总复杂度大约为 O ( n l o g n ) 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 用错位相减法算出公式求解,但很麻烦。
DrGilbert 2019.10.12

猜你喜欢

转载自blog.csdn.net/oampamp1/article/details/102513734