CCPC 2024 济南站
F. The Hermit
题目描述
知名丹药师戌狗在炼丹时发现精准剔除杂质能够提升丹药的灵韵。在日复一日地炼丹中,他发现了杂质的性质竟然与数学问题有千丝万缕的关系。由于你道行尚浅,戌狗决定以最直白的方式告诉你他需要解决的数学问题,而不是玄之又玄的炼丹问题。
给定两个正整数 \(n \leq m\),对 $ \{1,2,\dots,m\} $ 的所有大小为 \(n\) 的子集,计算以下问题的答案值求和,并对 \(998244353\) 取模:
- 在 \(n\) 个数的集合里,你可以删掉若干个数,使得集合的最小值不等于集合的最大公约数,问满足条件的方案里没有被删的数最多有多少个。如果无解,答案定义为 \(0\)。
集合的最大公约数定义为所有元素的共同约数中的最大值。例如,集合 $ \{6, 9, 15\} $ 的最大公约数是 \(3\)。
输入描述
输入一行,包含两个整数 \(n, m\) \((1 \leq n \leq m \leq 10^5)\)。
输出描述
输出一个整数表示答案,对 \(998244353\) 取模。
解题思路
对于 $ \{1, \dots, m\} $ 的所有大小为 \(n\) 的子集进行操作,可以考虑每个元素被删了多少次,从\(nC_m^n\)减去得到答案,而对于一个元素 \(x\) ,如果它会被删除,则说明
- 集合前一部分满足 $ \{a,ab,\dots,abc \dots x\} $ 形式,即后一个数是前一个数的倍数(称这个部分为倍数链,它的长度一定不会超过 \(log_2 x + 1\))
- 集合后一部分满足 $ \{x,2x,\dots,nx\} $ 形式,即 \(x\) 后面的数都是 \(x\) 的倍数
前一部分可以直接枚举倍数求得 \(f_{i,x}\) ,表示倍数链长度为 \(i\),且当前的数是 \(x\) 的方案数
后一部分可以直接在倍数里任选
代码实现
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e5 + 10;
const int MOD = 998244353;
std::vector<i64> fac(N + 1, 1), invfac(N + 1, 1);
i64 ksm(i64 a, i64 n, i64 MOD) {
i64 ans = 1;
a = (a % MOD + MOD) % MOD;
while (n) {
if (n & 1) {
ans = (a * ans) % MOD;
}
a = (a * a) % MOD;
n >>= 1;
}
return ans;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % MOD;
}
invfac[n] = ksm(fac[n], MOD - 2, MOD);
for (int i = n - 1; i >= 0; i--) {
invfac[i] = invfac[i + 1] * (i + 1) % MOD;
}
}
i64 C(int n, int m) { // 组合数
if (m > n || m < 0) {
return 0;
}
return fac[n] * invfac[m] % MOD * invfac[n - m] % MOD;
}
i64 A(int n, int m) { // 排列数
if (m > n || m < 0) {
return 0;
}
return fac[n] * invfac[n - m] % MOD;
}
i64 catalan(int n) { // 卡特兰数
if (n < 0) {
return 0;
}
return C(2 * n, n) * ksm(n + 1, MOD - 2, MOD) % MOD;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
init(N);
int m, n;
std::cin >> m >> n;
std::unordered_map<int, std::unordered_map<int, int>> f;
for (int x = 1; x <= m; x++) {
f[1][x] = 1;
}
int ans = n * C(m, n) % MOD;
for (int i = 1; i <= n; i++) { // 枚举长度
for (auto [x, y] : f[i]) {
ans = ans - f[i][x] * C(m / x - 1, n - i) % MOD; // 直接枚举倍数
ans %= MOD;
if (ans < 0) {
ans += MOD;
}
for (int j = 2; j * x <= m && i + 1 <= n; j++) { // 更新倍数链
f[i + 1][j * x] = (f[i + 1][j * x] + f[i][x]) % MOD;
}
}
}
std::cout << ans << '\n';
}

浙公网安备 33010602011771号