📅  最后修改于: 2023-12-03 15:27:35.575000             🧑  作者: Mango
给定一个长度为 $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;
}