JZOJ 4161 于神之怒 / BZOJ 4407 于神之怒加强版 莫比乌斯反演 时间复杂度分析

  本文使用 Markdown。如果你需要 Markdown 源码,请与我联系。

          • 传送门
          • 加强版传送门
        • 单组询问
          • 思路
            • ①枚举 gcd
            • ②时间复杂度
            • ③积性函数
            • ④时间复杂度分析
        • 多组询问
          • 思路
            • ①化简
            • ②积性函数
          • 参考代码

传送门
加强版传送门

单组询问

思路
①枚举 gcd

  这是一个最通用最基本的思路(然而一开始做的时候我却去想怎么把 (gcd(x,y))k ( gcd ( x , y ) ) k 转化为别的形式)。知道它后,就不难将原问题转变为如下式子(完全不会莫比乌斯反演的同学请看教程):

=g=1gk(d=1μ(d)ngdmgd) = ∑ g = 1 g k ( ∑ d = 1 μ ( d ) ⌊ ⌊ n g ⌋ d ⌋ ⌊ ⌊ m g ⌋ d ⌋ )

  其中形如

a=1 ∑ a = 1

的和式,表示从 1 1 开始枚举到一定值,使得要求和的内容在该值之后均为 0 0

②时间复杂度

  首先我们需要证明目前算法的时间复杂度。

  根据常识,计算内部和式的时间复杂度为 O(ng) O ( n g ) ,计算 gk g k 的时间复杂度为 O(logk) O ( log ⁡ k ) 。那么算法的整体时间复杂度为:

O(logk(n1+n2++nn)) O ( log ⁡ k ( n 1 + n 2 + ⋯ + n n ) )

  过去,我们知道这么一个事实:

O(n1+n2++nn)=O(nlogn) O ( n 1 + n 2 + ⋯ + n n ) = O ( n log ⁡ n )

  那这道题的时间复杂度能够类比吗?

  事实上,可以证明以下事实:

11+12++1n<2n 1 1 + 1 2 + ⋯ + 1 n < 2 n

  所以以上算法的时间复杂度为 O(nlogk) O ( n log ⁡ k ) ,不足以通过此题。

③积性函数

  怎么初始化 xk x k ?一开始我想到了可以线性递推的 kx k x ,但是这个递推方法无法解决 xk x k

  事实上, 可以证明这样一个结论:积性函数与积性函数的乘积仍然是积性函数;完全积性函数与完全积性函数的乘积仍然是完全积性函数。

证明
  设积性函数 f f g g ,我们定义 f f g g 的乘积 h h 为:

h(x)=(fg)(x)=f(x)g(x) h ( x ) = ( f ⋅ g ) ( x ) = f ( x ) ⋅ g ( x )

  可以得到:

h(1)=f(1)g(1)=1 h ( 1 ) = f ( 1 ) g ( 1 ) = 1

h(x)=i=1kf(prii)g(prii) h ( x ) = ∏ i = 1 k f ( p i r i ) ⋅ g ( p i r i )

  有:

h(xprk+1k+1)=i=1k+1f(prii)g(prii)=(i=1kf(prii)g(prii))f(prk+1k+1)g(prk+1k+1)=h(x)h(prk+1k+1) h ( x · p k + 1 r k + 1 ) = ∏ i = 1 k + 1 f ( p i r i ) ⋅ g ( p i r i ) = ( ∏ i = 1 k f ( p i r i ) ⋅ g ( p i r i ) ) ⋅ f ( p k + 1 r k + 1 ) ⋅ g ( p k + 1 r k + 1 ) = h ( x ) ⋅ h ( p k + 1 r k + 1 )

  得证!完全积性函数也使用数学归纳法证明。

  于是我们可以使用欧拉筛初始化 gk g k 。由于它相当于:

idk i d k

  所以它是一个完全积性函数,可以直接使用欧拉筛初始化。

④时间复杂度分析

  可以知道小于等于 n n 的质数个数 π(n) π ( n ) 有如下性质:

π(n)nlnn π ( n ) ≈ n ln ⁡ n

  计算质数 pk p k 的时间复杂度为 O(logk) O ( log ⁡ k ) ,计算合数 gk g k 的时间复杂度为 O(1) O ( 1 ) ,所以计算 ak a k 的欧拉筛的均摊时间复杂度为 O(n) O ( n )

  至此,我们得到了时间复杂度为 O(n) O ( n ) - O(n) O ( n ) 的算法,足以通过此题。

多组询问

思路
①化简

  考虑进行进行化简:

g=1gk(d=1μ(d)ngdmgd)=g=1gk(d=1μ(d)ngdmgd)=t=1ntmt ∑ g = 1 g k ( ∑ d = 1 μ ( d ) ⌊ ⌊ n g ⌋ d ⌋ ⌊ ⌊ m g ⌋ d ⌋ ) = ∑ g = 1 g k ⋅ ( ∑ d = 1 μ ( d ) ⌊ n g d ⌋ ⌊ m g d ⌋ ) = ∑ t = 1 ⌊ n t ⌋ ⌊ m t ⌋ ⋯

  观察发现,我们实际上是枚举的 t=gd t = g d ,也就是说,我们将会得到:

=t=1ntmtgtgkμ(tg) = ∑ t = 1 ⌊ n t ⌋ ⌊ m t ⌋ ( ∑ g ∣ t g k μ ( t g ) )

  如果我们能够预处理出右边和式的前缀和,我们便能在 O(n) O ( n ) 的时间复杂度处理单组询问。

②积性函数

  将右边的和式写作狄利克雷卷积的形式可以得:

F=(idk)μ F = ( i d k ) ∗ μ

  之前已经证明, idk i d k 是一个完全积性函数,而 μ μ 是一个积性函数,因此,由狄利克雷卷积的性质, F F 也是一个积性函数。

  因此,我们可以使用欧拉筛初始化 F F 。可以轻松得到如下边界条件:

F(1)=1F(p)=pkμ(p)=pk1 F ( 1 ) = 1 F ( p ) = p k − μ ( p ) = p k − 1

  只需要知道如何计算 F(pt) F ( p t ) ,就可以使用欧拉筛了。可以发现:

F(pt)=ptkμ(1)+p(t1)kμ(p)+=ptkp(t1)k F ( p t ) = p t k μ ( 1 ) + p ( t − 1 ) k μ ( p ) + ⋯ = p t k − p ( t − 1 ) k

  于是我们有:

F(pt+1)=F(pt)pk F ( p t + 1 ) = F ( p t ) ⋅ p k

  这样,我们就能在 O(n) O ( n ) 的时间复杂度内预处理出 F F

参考代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
typedef long long INT;
using std::cin;
using std::cout;
using std::endl;
INT readIn()
{
    INT a = 0;
    bool minus = false;
    char ch = getchar();
    while (!(ch == '-' || (ch >= '0' && ch <= '9'))) ch = getchar();
    if (ch == '-')
    {
        minus = true;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        a = a * 10 + (ch - '0');
        ch = getchar();
    }
    if (minus) a = -a;
    return a;
}
void printOut(INT x)
{
    char buffer[20];
    INT length = 0;
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    do
    {
        buffer[length++] = x % 10 + '0';
        x /= 10;
    } while (x);
    do
    {
        putchar(buffer[--length]);
    } while (length);
    putchar('\n');
}

const int mod = int(1e9) + 7;
const int maxn = int(5e6) + 5;
const int to = int(5e6);
int k;

int pwr[maxn];
int mu[maxn];
long long g[maxn];
int prime[348514];
bool isntPrime[maxn];
int power(int x, int y)
{
    int ret = 1;
    while (y)
    {
        if (y & 1) ret = (long long)ret  * x % mod;
        x = (long long)x * x % mod;
        y >>= 1;
    }
    return ret;
}
void init()
{
    pwr[1] = 1;
    mu[1] = 1;
    g[1] = 1;
    isntPrime[1] = true;
    int& num = prime[0];
    for (int i = 2; i <= to; i++)
    {
        if (!isntPrime[i])
        {
            prime[++num] = i;
            mu[i] = -1;
            pwr[i] = power(i, k);
            g[i] = (mu[i] + pwr[i]) % mod;
        }
        for (int j = 1, p = prime[j], s = i * p; j <= num && s <= to; j++, p = prime[j], s = i * p)
        {
            isntPrime[s] = true;
            pwr[s] = (long long)pwr[i] * pwr[p] % mod;
            if (i % p)
            {
                mu[s] = -mu[i];
                g[s] = (long long)g[i] * g[p] % mod;
            }
            else
            {
                mu[s] = 0;
                g[s] = (long long)g[i] * pwr[p] % mod;
                break;
            }
        }
    }
    for (int i = 2; i <= to; i++)
        g[i] = (g[i] + g[i - 1]) % mod;
}

int n, m;

void run()
{
    int T = readIn();
    k = readIn();
    init();
    while (T--)
    {
        n = readIn();
        m = readIn();
        if (n < m) std::swap(n, m);

        long long ans = 0;
        for (int i = 1, t; i <= m; i = t + 1)
        {
            t = std::min(n / (n / i), m / (m / i));
            ans = (ans + (long long)(g[t] - g[i - 1] + mod) * (n / i) % mod * (m / i) % mod) % mod;
        }
        printOut(ans);
    }
}

int main()
{
    run();
    return 0;
}

你可能感兴趣的:(OI,数学)