当前位置: 首页 > news >正文

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;
}

http://www.mrgr.cn/news/36139.html

相关文章:

  • 深入解析网络通信关键要素:IP 协议、DNS 及相关技术
  • 股价上涨210%后,目标价又被花旗大幅上调,AppLovin还能继续上涨吗?
  • 前端文件上传全过程
  • PG逻辑订阅功能
  • 尚硅谷MyBatis笔记
  • Spring 的作用和优势
  • 省市区乡村五级地址库
  • C/C++语言基础--C++类数据、静态与非静态、常成员、友员、成员变量与函数指针等相关知识点
  • 3. 轴指令(omron 机器自动化控制器)——>MC_MoveZeroPosition
  • uboot — uboot命令的使用
  • 如何在 Linux 中管理和清理日志文件( `find` 命令按时间批量删除日志)
  • 2024.9.25 作业和思维导图
  • 线程安全的数据结构使用起来一定线程安全吗?
  • 将ipad作为数位板使用教程/出现延迟拖拽怎么办?
  • MySql 从入门到入门
  • 【笔记篇】一篇文章搞定Spring框架
  • WordPress LearnPress插件 SQL注入复现(CVE-2024-8522)
  • 每天分享一个FPGA开源代码(6)- 浮点数运算
  • 回归阅读第一本:《瓦尔纳宝典》
  • windows GetUserNameEx api使用c++