CCPC 2024 濟南站

udiandianis發表於2024-11-20

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;
            }
        }
    }
    cout << ans << '\n';
}

相關文章