Educational Codeforces Round 20 F. Coprime Subsequences(DP+容斥)

原题链接: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]=xycnt[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]gcd4gcd6=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]gcd2gcd3gcd4gcd5gcd6=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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值