📌  相关文章
📜  给定数组的所有非空子序列中不同 GCD 的计数(1)

📅  最后修改于: 2023-12-03 15:27:35.575000             🧑  作者: Mango

给定数组的所有非空子序列中不同 GCD 的计数

给定一个长度为 $n$ 的整数数组 $a$,计算所有非空子序列的不同的最大公约数(GCD)的数量。答案模 $998244353$。

解法

对于一个整数 $x$,以 $x$ 为最大公约数的子序列数量为 $2^{cnt(x)}-1$,其中 $cnt(x)$ 表示 $a$ 中有多少个数可以整除 $x$。这个式子很好证明,将 $x$ 的指标 $i$ 看做是一个物品,可以选择 选或不选,此时的方案数就是 $2^{cnt(x)}$,但是不能选择空集(即一定要选),所以方案数要减去 1。

对于一个子序列,最大公约数 $x$,如果 $x$ 在多个子序列中出现,那么这些子序列可以分成两类:属于同一个长度为 $cnt(x)$ 的子集和不属于。而一旦确定了一个子序列属于哪个子集,这个子序列就和 $x$ 一一对应了。因此,最终答案即为所有不同的 $x$ 的数量加上所有不同 $x$ 所处同一子集的方案数。

计算不同的 $x$ 直接枚举即可,在统计方案数的时候,可以使用容斥原理,假设有 $k$ 个最大公约数为 $x$ 的子序列必须属于同一个子集,则贡献为 $2^{k}-1$。而有些子序列不被包含在这 $k$ 个子序列中,则方案数为 $2^{n-k}-1$。将这些方案数乘起来即可得到答案。

时间复杂度为 $O(n2^{n/2})$。

代码
const int mod = 998244353;
const int N = 3005;
int n, a[N], cnt[N];
int f[2][N], ans;
void add(int &x, int y) {
    x += y;
    if (x >= mod) x -= mod;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        cnt[a[i]]++;
    }
    for (int i = 1; i < N; i++)
        if (cnt[i]) {
            memset(f[0], 0, sizeof(f[0]));
            memset(f[1], 0, sizeof(f[1]));
            f[0][0] = f[1][0] = 1;
            int t = 0;
            for (int j = 1; j <= n; j++) {
                if (a[j] % i) continue;
                t ^= 1;
                memcpy(f[t], f[t ^ 1], sizeof(f[0]));
                int k = cnt[i];
                while (k--) {
                    for (int l = j; l >= 1; l--) add(f[t][l], f[t ^ 1][l - 1]);
                }
            }
            int res = 0, k = cnt[i];
            while (k--) add(res, f[t][k]);
            ans = (ans + 1LL * res * (1LL << (cnt[i] - res)) % mod + mod) % mod;
        }
    cout << ans << endl;
    return 0;
}

返回markdown格式:

## 给定数组的所有非空子序列中不同 GCD 的计数

给定一个长度为 $n$ 的整数数组 $a$,计算所有非空子序列的不同的最大公约数(GCD)的数量。答案模 $998244353$。

### 解法

对于一个整数 $x$,以 $x$ 为最大公约数的子序列数量为 $2^{cnt(x)}-1$,其中 $cnt(x)$ 表示 $a$ 中有多少个数可以整除 $x$。这个式子很好证明,将 $x$ 的指标 $i$ 看做是一个物品,可以选择 选或不选,此时的方案数就是 $2^{cnt(x)}$,但是不能选择空集(即一定要选),所以方案数要减去 1。

对于一个子序列,最大公约数 $x$,如果 $x$ 在多个子序列中出现,那么这些子序列可以分成两类:属于同一个长度为 $cnt(x)$ 的子集和不属于。而一旦确定了一个子序列属于哪个子集,这个子序列就和 $x$ 一一对应了。因此,最终答案即为所有不同的 $x$ 的数量加上所有不同 $x$ 所处同一子集的方案数。

计算不同的 $x$ 直接枚举即可,在统计方案数的时候,可以使用容斥原理,假设有 $k$ 个最大公约数为 $x$ 的子序列必须属于同一个子集,则贡献为 $2^{k}-1$。而有些子序列不被包含在这 $k$ 个子序列中,则方案数为 $2^{n-k}-1$。将这些方案数乘起来即可得到答案。

时间复杂度为 $O(n2^{n/2})$。

### 代码

```cpp
const int mod = 998244353;
const int N = 3005;
int n, a[N], cnt[N];
int f[2][N], ans;
void add(int &x, int y) {
    x += y;
    if (x >= mod) x -= mod;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        cnt[a[i]]++;
    }
    for (int i = 1; i < N; i++)
        if (cnt[i]) {
            memset(f[0], 0, sizeof(f[0]));
            memset(f[1], 0, sizeof(f[1]));
            f[0][0] = f[1][0] = 1;
            int t = 0;
            for (int j = 1; j <= n; j++) {
                if (a[j] % i) continue;
                t ^= 1;
                memcpy(f[t], f[t ^ 1], sizeof(f[0]));
                int k = cnt[i];
                while (k--) {
                    for (int l = j; l >= 1; l--) add(f[t][l], f[t ^ 1][l - 1]);
                }
            }
            int res = 0, k = cnt[i];
            while (k--) add(res, f[t][k]);
            ans = (ans + 1LL * res * (1LL << (cnt[i] - res)) % mod + mod) % mod;
        }
    cout << ans << endl;
    return 0;
}