CF2037G - Natlan Exploring 题解

又来到我们最喜欢的数论环节了。

题面

纳特兰地区由 \(n\) 座城市组成,每座城市的吸引力值为 \(a_i\) 。从城市 \(i\) 到城市 \(j\) 之间存在一条有向边,当且仅当 \(i < j\)\(\gcd(a_i, a_j)\neq 1\) ,其中 \(\gcd(x, y)\) 表示整数 \(x\)\(y\)最大公约数 (GCD)

从城市 \(1\) 出发,你的任务是确定到达城市 \(n\) 的不同路径的总数,答案对 \(998,244,353\) 取模。当且仅当所访问的城市集合不同时,两条路径才是不同的。


题解

一眼看过去会是一个方案数 \(dp\),但是不这么好直接干出来,因为边是不定的,而且无法全部建出来。

不妨设 \(f(i)\) 表示到达城市 \(i\) 的方案数,容易得到:

\[f(i) = \sum_{j = 1}^{i - 1} f(j) [\gcd(a_i, a_j) \neq 1] \]

看见这玩意直接来一发莫比乌斯反演。

\[f(i) = \sum_{j = 1}^{i - 1} f(j)\sum_{\substack{d \mid \gcd(a_i, a_j)\\d \neq 1}}\mu(d) \]

移一下项,把 \(a_i\) 放到前面去,\(a_j\) 留在后面:

\[f(i) = \sum_{\substack{d \mid a_i\\d \neq 1}}\mu(d)\sum_{j = 1}^{i - 1} f(j)[d \mid a_j] \]

此刻,我们发现一个非常好的一点,后者是 \(d, i\) 相关独立的,可以提出来。

不妨设 \(g(n, d) = \sum\limits_{j = 1}^{n}f(j)[d \mid a_j]\),容易发现:

\[g(i, d) = g(i - 1, d) + f(i)[d \mid a_i] \]

在计算 \(f(i)\) 的过程中可以顺带把 \(g(i, d)\) 给计算出来。

每次算完 \(f(i)\) 更新 \(g(i - 1, d)\),因此可以降下一维,满足空间需求。

线性筛筛出莫比乌斯函数,预处理因数 \(O(A\ln{A})\),或枚举因数 \(O(n\sqrt{A})\) 即可。

不过预处理比枚举因数慢是令我没想到的。

参考代码(预处理)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, M = 2e5 + 10, mod = 998244353;
int n, a[M];
int st[N], primes[N], cnt, mu[N];
vector<int> factors[N];
ll f[M], g[N];

void init()
{
    mu[1] = 1;
    for (int i = 2; i < N; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i, mu[i] = 1;
        for (int j = 0; primes[j] * i < N; j ++ )
        {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) break;
            mu[i * primes[j]] = -mu[i];
        }
    }
    for (int i = 2; i < N; i ++ )
        for (int j = i; j < N; j += i)
            factors[j].push_back(i);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n, init();
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    f[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        for (auto d : factors[a[i - 1]])
            if (a[i - 1] % d == 0)
                (g[d] += f[i - 1]) %= mod;
        for (auto d : factors[a[i]])
            if (mu[d])
                (f[i] += mu[d] * g[d]) %= mod;
    }
    cout << (f[n] + mod) % mod;
    return 0;
}

参考代码(枚举)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, M = 2e5 + 10, mod = 998244353;
int n, a[M];
int st[N], primes[N], cnt, mu[N];
vector<int> factors[N];
ll f[M], g[N];

void init()
{
    mu[1] = 1;
    for (int i = 2; i < N; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i, mu[i] = 1;
        for (int j = 0; primes[j] * i < N; j ++ )
        {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) break;
            mu[i * primes[j]] = -mu[i];
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n, init();
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    f[1] = 1;
    for (int i = 2; i <= n; i ++ )
    {
        for (int d = 1; d * d <= a[i - 1]; d ++ )
        {
            if (a[i - 1] % d) continue;
            if (d != 1) (g[d] += f[i - 1]) %= mod;
            if (d * d != a[i - 1]) (g[a[i - 1] / d] += f[i - 1]) %= mod;
        }
        for (int d = 1; d * d <= a[i]; d ++ )
        {
            if (a[i] % d) continue;
            if (d != 1) (f[i] += mu[d] * g[d]) %= mod;
            if (d * d != a[i]) (f[i] += mu[a[i] / d] * g[a[i] / d]) %= mod;
        }
    }
    cout << (f[n] + mod) % mod;
    return 0;
}
posted @ 2024-11-29 17:48  YipChip  阅读(7)  评论(0编辑  收藏  举报