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;
}