原题链接:F. Coprime Subsequences
题目大意:
给出一个长度为 n n n 的数组 a a a,询问有多少个非空 子序列 b b b 满足 gcd ( b 1 , b 2 , . . . , b k ) = 1 \gcd(b_{1},b_{2},...,b_{k})=1 gcd(b1,b2,...,bk)=1 ,答案对 1 0 9 + 7 10^{9}+7 109+7 取模。
解题思路:
子序列的问题,我们只需要知道某个数字有和没有就好了。
考虑容斥,我们定义 d p [ i ] dp[i] dp[i] 表示子序列 b b b 的 gcd \gcd gcd 至少为 i i i 的倍数方案数,考虑一下这个怎么求。
注意这个 gcd \gcd gcd 至少为 i i i 的倍数的含义。
即考虑 gcd = 2 \gcd=2 gcd=2 时,我们 d p [ 2 ] dp[2] dp[2] 同时也包含了 gcd = 6 , 12 , 22 \gcd = 6,12,22 gcd=6,12,22 这类的情况。
不理解这一步的可以思考一下如下的数列
假设原数组为 [ 1 , 2 , 4 , 7 , 6 , 2 , 8 , 9 ] [1,2,4,7,6,2,8,9] [1,2,4,7,6,2,8,9]
考虑此时 gcd \gcd gcd 至少 为 2 2 2 的倍数,我们一定只能从这些数字中选。
[ 2 , 4 , 6 , 2 , 8 ] [2,4,6,2,8] [2,4,6,2,8]
但是我们可能选出 gcd ( 4 , 8 ) = 4 , gcd ( 4 , 6 , 8 ) = 2 , . . . \gcd(4,8)=4,\gcd(4,6,8)=2,... gcd(4,8)=4,gcd(4,6,8)=2,... 之类的情况,但此时 gcd \gcd gcd 的值都至少是 2 2 2 的倍数。
此时的方案数就是 d p [ 2 ] dp[2] dp[2] 的含义。
考虑将每个数字装入桶 c n t [ x ] cnt[x] cnt[x],定义 c n t [ x ] cnt[x] cnt[x] 为数字 x x x 的倍数 y y y 出现的次数,显然:
c n t [ x ] = ∑ x ∣ y c n t [ y ] \begin{aligned} cnt[x] &= \sum_{x|y}cnt[y] \end{aligned} cnt[x]=x∣y∑cnt[y]
这个可以直接 O ( V log V ) O(V \log V) O(VlogV) 做即可。
由于要求非空子序列,那么显然可得:
d p [ x ] = 2 c n t [ x ] − 1 \begin{aligned} dp[x] &= 2^{cnt[x]} - 1 \end{aligned} dp[x]=2cnt[x]−1
此时我们的 d p [ x ] dp[x] dp[x] 就知道了,但显然我们要求的不是 gcd \gcd gcd 至少为 x x x 的方案数,而是 恰好 为 x x x 的方案数。
考虑容斥,如 d p [ 2 ] dp[2] dp[2] 中包含了 gcd = 2 , 4 , 6 , 8 , . . . \gcd =2,4,6,8,... gcd=2,4,6,8,... 的信息,那么我们直接考虑减去这些信息就好了。
怎么减?既然 d p [ 2 ] dp[2] dp[2] 中包含了若干个信息,那么我们从只包含自身的信息(即最尾部的信息)倒着开始做减法不就好了吗?
假设 a i ∈ [ 1 , 6 ] a_{i} \in [1, 6] ai∈[1,6],那么我们的 d p [ x ] dp[x] dp[x] 的构成为:
d p [ 1 ] = g c d 1 + g c d 2 + g c d 3 + g c d 4 + g c d 5 + g c d 6 dp[1]=gcd_{1}+gcd_{2}+gcd_{3}+gcd_{4}+gcd_{5}+gcd_{6} dp[1]=gcd1+gcd2+gcd3+gcd4+gcd5+gcd6
d p [ 2 ] = g c d 2 + g c d 4 + g c d 6 dp[2]=gcd_{2}+gcd_{4}+gcd_{6} dp[2]=gcd2+gcd4+gcd6
d p [ 3 ] = g c d 3 + g c d 6 dp[3]=gcd_{3}+gcd_{6} dp[3]=gcd3+gcd6
d p [ 4 ] = g c d 4 dp[4]=gcd_{4} dp[4]=gcd4
d p [ 5 ] = g c d 5 dp[5]=gcd_{5} dp[5]=gcd5
d p [ 6 ] = g c d 6 dp[6]=gcd_{6} dp[6]=gcd6(上面的 g c d x gcd_{x} gcdx 表示 gcd \gcd gcd 恰好等于 x x x 时的方案数)
我们要求 gcd \gcd gcd 恰好 等于 x x x 的方案数,那么答案就呼之欲出了:
g c d 3 = d p [ 3 ] − g c d 6 = d p [ 3 ] − d p [ 6 ] g c d 2 = d p [ 2 ] − g c d 4 − g c d 6 = d p [ 2 ] − d p [ 4 ] − d p [ 6 ] \begin{aligned} gcd_{3} &= dp[3]-gcd_{6}\\ &=dp[3] - dp[6]\\\\ gcd_{2} &= dp[2]-gcd_{4}-gcd_{6}\\ &=dp[2] - dp[4]-dp[6] \end{aligned} gcd3gcd2=dp[3]−gcd6=dp[3]−dp[6]=dp[2]−gcd4−gcd6=dp[2]−dp[4]−dp[6]
最后使 d p [ 3 ] = g c d 3 , d p [ 2 ] = g c d 2 dp[3] = gcd_{3},dp[2]=gcd_{2} dp[3]=gcd3,dp[2]=gcd2 此时:
d p [ 1 ] = g c d 1 + g c d 2 + g c d 3 + g c d 4 + g c d 5 + g c d 6 dp[1]=gcd_{1}+gcd_{2}+gcd_{3}+gcd_{4}+gcd_{5}+gcd_{6} dp[1]=gcd1+gcd2+gcd3+gcd4+gcd5+gcd6
d p [ 2 ] = g c d 2 dp[2]=gcd_{2} dp[2]=gcd2
d p [ 3 ] = g c d 3 dp[3]=gcd_{3} dp[3]=gcd3
d p [ 4 ] = g c d 4 dp[4]=gcd_{4} dp[4]=gcd4
d p [ 5 ] = g c d 5 dp[5]=gcd_{5} dp[5]=gcd5
d p [ 6 ] = g c d 6 dp[6]=gcd_{6} dp[6]=gcd6
那么:
g c d 1 = d p [ 1 ] − g c d 2 − g c d 3 − g c d 4 − g c d 5 − g c d 6 = d p [ 1 ] − d p [ 2 ] − d p [ 3 ] − d p [ 4 ] − d p [ 5 ] − d p [ 6 ] \begin{aligned} gcd_{1}&=dp[1] - gcd_{2}-gcd_{3}-gcd_{4}-gcd_{5}-gcd_{6}\\ &= dp[1] - dp[2] - dp[3] - dp[4] - dp[5] - dp[6] \end{aligned} gcd1=dp[1]−gcd2−gcd3−gcd4−gcd5−gcd6=dp[1]−dp[2]−dp[3]−dp[4]−dp[5]−dp[6]
那么答案就在 d p [ 1 ] dp[1] dp[1] 取得了。
时间复杂度: O ( V log V ) O(V \log V) O(VlogV)
#include <bits/stdc++.h>
using i64 = long long;
const int mod = 1e9 + 7;
i64 qpow(i64 a, i64 b) {
i64 res = 1; a %= mod;
for (; b; b >>= 1, a = a * a % mod) {
if (b & 1) res = res * a % mod;
}
return res;
}
const int N = 1e6;
i64 cnt[N + 1], dp[N + 1];
void solve() {
int n;
std::cin >> n;
for (int i = 1; i <= n; ++i) {
int x;
std::cin >> x;
++cnt[x];
}
for (int i = 1; i <= N; ++i) {
for (int j = i + i; j <= N; j += i) {
cnt[i] += cnt[j];
}
dp[i] = (qpow(2, cnt[i]) - 1 + mod) % mod;
}
for (int i = N; i >= 1; --i) {
for (int j = i + i; j <= N; j += i) {
dp[i] = (dp[i] - dp[j] + mod) % mod;
}
}
std::cout << dp[1] << '\n';
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
//std::cin >> t;
while (t--) {
solve();
}
return 0;
}