BZOJ4833:「Lydsy1704月赛」最小公倍佩尔数 – 题解

主要思路

虽然是个套路题,但是为了完全体会 min-max 反演的魅力之处,我决定写篇博客来强化一下。

我们大概推一推能发现:

\[ f_n = 2f_{n – 1} + f_{n – 2} \]

那么,我们现在就是要求:

\[ \sum_{i = 1}^n i \text{lcm}_{j = 1}^i f_j \]

如何把多个数做 LCM 的过程通过 min-max 反演进行优化呢?我们首先把每个数字看作是一个集合,那么这个集合的权就是集合中的因子的积。那么,两个数的最大公因数可以被理解为这两个集合的交。既然我们要求出最大的集合(最小公倍数)我们可以列出关系:

\[ \max(S)=\sum_{\emptyset\neq T\subseteq S}(-1)^{|T|-1}\min(T) \\  \text{lcm}_{i = 1}^n f_i = \prod_{T \subset S, T \neq \emptyset} \gcd(T)^{(-1)^{|T| – 1}} \]

然后我们知道一件事,就是 \(\gcd(f_a, f_b) = f_{\gcd(a, b)}\),然后带入进去:

\[ \text{lcm}_{i = 1}^n f_i = \prod_{T \subset S, T \neq \emptyset} f_{\gcd(T)}^{(-1)^{|T| – 1}} \]

喜闻乐见,可以考虑把指数的那个贡献计算出来:

\[ \text{lcm}_{i = 1}^n f_i = \prod_{d = 1}^n f_d^{\sum_{d | x} [x \leq n] \mu(\frac{x}{d})} \]

反过来即可:

\[ \text{lcm}_{i = 1}^n f_i = \prod_{d = 1}^n \prod_{i | d} f_i^{\mu(\frac{d}{i})} \]

代码

// BZ4833.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1e6 + 200;

int T, n, mod, primes[MAX_N], tot, mu[MAX_N], f[MAX_N], finv[MAX_N], units[MAX_N];
bool vis[MAX_N];

int fpow(int bas, int tim)
{
    int ret = 1;
    while (tim)
    {
        if (tim & 1)
            ret = 1LL * ret * bas % mod;
        bas = 1LL * bas * bas % mod;
        tim >>= 1;
    }
    return ret;
}

void sieve()
{
    mu[1] = 1;
    for (int i = 2; i < MAX_N; i++)
    {
        if (!vis[i])
            primes[++tot] = i, mu[i] = -1;
        for (int j = 1; j <= tot && 1LL * i * primes[j] < MAX_N; j++)
        {
            vis[i * primes[j]] = true;
            if (i % primes[j] == 0)
            {
                mu[i * primes[j]] = 0;
                break;
            }
            mu[i * primes[j]] = -mu[i];
        }
    }
}

int main()
{
    scanf("%d", &T), sieve();
    while (T--)
    {
        scanf("%d%d", &n, &mod);
        f[1] = 1, finv[1] = 1;
        for (int i = 2; i <= n; i++)
            f[i] = (2LL * f[i - 1] + f[i - 2]) % mod, finv[i] = fpow(f[i], mod - 2);
        for (int i = 1; i <= n; i++)
            units[i] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = i; j <= n; j += i)
                if (mu[j / i] < 0)
                    units[j] = 1LL * units[j] * finv[i] % mod;
                else if (mu[j / i] > 0)
                    units[j] = 1LL * units[j] * f[i] % mod;
        for (int i = 2; i <= n; i++)
            units[i] = 1LL * units[i - 1] * units[i] % mod;
        int ans = 0;
        for (int i = 1; i <= n; i++)
            ans = (0LL + ans + 1LL * units[i] * i % mod) % mod;
        printf("%d\n", ans);
    }
    return 0;
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *