Codeforces CodeTON Round 3 D. Count GCD【状压、容斥原理计数】

2024-04-06 01:12

本文主要是介绍Codeforces CodeTON Round 3 D. Count GCD【状压、容斥原理计数】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

D. Count GCD

D

题意

给定一个长度为 n n n 的正整数数组 a a a ∀ 1 ≤ i ≤ n , a i ≤ m \forall 1 \leq i \leq n,a_i \leq m ∀1inaim

先要要统计满足以下条件的数组 b b b 的数量:

  • ∀ 1 ≤ i ≤ n , b i ≤ m \forall 1 \leq i \leq n,b_i \leq m ∀1inbim
  • g c d ( b 1 , b 2 , b 3 , . . . b i ) = a i gcd(b_1,b_2,b_3,...b_i) = a_i gcd(b1,b2,b3,...bi)=ai
思路

可以发现在当前的 i i i g c d ( a 1 , a 2 , . . . a i − 1 ) = a i − 1 gcd(a_1,a_2,...a_{i -1}) = a_{i -1} gcd(a1,a2,...ai1)=ai1,那么只要 g c d ( a i − 1 , b i ) = a i gcd(a_{i - 1},b_i) = a_i gcd(ai1,bi)=ai 就可以了,那么我们可以得出结论: a i ∣ a i − 1 a_i \mid a_{i - 1} aiai1,也即是 a i − 1 a_{i - 1} ai1 a i a_i ai 的因子,同时 a i ∣ b i a_i \mid b_i aibi

这样子答案就是每个位置可选的数量累乘,问题是如何快速求出位置 i i i 可行的 b i b_i bi 数量

b i b_i bi 一定得是 a i a_i ai 的倍数,我们可以假设 k ⋅ a i = b i k \cdot a_i = b_i kai=bi,那么不同的 k k k 的数量就是 b i b_i bi 的数量
那么 g c d ( a i − 1 , b i ) = a i ⇒ g c d ( a i − 1 a i , k ) = 1 gcd(a_{i - 1}, b_i) = a_i \Rarr gcd(\dfrac{a_{i - 1}}{a_i}, k) = 1 gcd(ai1,bi)=aigcd(aiai1,k)=1,也就是 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 中出现的质因子不能在 k k k 中出现

为了快速得到 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 的所有质因子,我们发现 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 的质因子一定是 a 1 a_1 a1 的质因子。这是因为 a i ∣ a i − 1 ∣ . . . . ∣ a 2 ∣ a 1 a_i \mid a_{i - 1} \mid .... \mid a_2 \mid a_1 aiai1....a2a1;并且最小的 10 10 10 个质数相乘就已经大于 1 0 9 10^9 109 了!所以我们只需要最多 9 9 9 个不同的质数(不一定是最小的 9 9 9 个)就可以表示小于等于 1 0 9 10^9 109 的数了

我们只需要在 O ( m ) O(m) O(m) 下预处理 得出 a 1 a_1 a1质因子,然后对于每个 i > 2 i > 2 i>2,枚举 a 1 a_1 a1 的质因子,看看 a i a_i ai 是否被其整除,即可快速得出 a i a_i ai 的所有质因子,质因子数量都不超过 9 9 9 个!我们这里就可以考虑状压 + 容斥原理来计数了

容斥原理
集合 S S S 的子集 A 1 A_1 A1 具有性质 P 1 P_1 P1,子集 A 2 A_2 A2 具有性质 P 2 P_2 P2,… A n A_n An 具有性质 P n P_n Pn


(1)集合 S S S不具有性质 P 1 , P 2 , . . . , P n P_1,P_2,...,P_n P1,P2,...,Pn 的对象个数为:
∣ A 1 ‾ ∩ A 2 ‾ , . . . , ∩ A n ‾ ∣ = ∣ S ∣ − ∑ ∣ A i ∣ + ∑ ∣ A i ∩ A j ∣ − ∑ ∣ A i ∩ A j ∩ A k ∣ + . . . + ( − 1 ) n ∣ A 1 ∩ A 2 ∩ . . . ∩ A n ∣ | \overline{A_1} \cap \overline{A_2}, ..., \cap \overline{A_n}| = |S| - \sum |A_i| + \sum |A_i \cap A_j| - \sum |A_i \cap A_j \cap A_k| + ... + (-1)^{n} |A_1 \cap A_2 \cap ... \cap A_n| A1A2,...,An=SAi+AiAjAiAjAk+...+(1)nA1A2...An


(2)集合 S S S至少具有性质 P 1 , P 2 , . . . P n P_1,P_2,...P_n P1,P2,...Pn 之一的对象个数为:
∣ A 1 ∪ A 2 , . . . , ∪ A n ∣ = ∑ ∣ A i ∣ − ∑ ∣ A i ∩ A j ∣ + ∑ ∣ A i ∩ A j ∩ A k ∣ + . . . + ( − 1 ) n + 1 ∣ A 1 ∩ A 2 ∩ . . . ∩ A n ∣ | A_1 \cup A_2, ..., \cup A_n| = \sum |A_i| - \sum |A_i \cap A_j| + \sum |A_i \cap A_j \cap A_k| + ... + (-1)^{n + 1} |A_1 \cap A_2 \cap ... \cap A_n| A1A2,...,An=AiAiAj+AiAjAk+...+(1)n+1A1A2...An

因为性质很少,最多只有 9 9 9 个(分别被不同的 9 9 9 个质数整除),我们要求出与 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 互质的 k k k 的数量,也就是 k k k 不能拥有 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 的质因子,也就是 k k k 不具有性质 P 1 , P 2 , . . . P k P_1,P_2,...P_k P1,P2,...Pk k k k a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 质因子数),只需要用容斥原理简单地通过整除得到即可,注意 k ≤ m a i k \leq \dfrac{m}{a_i} kaim

时间复杂度: O ( m + 9 ⋅ 2 9 ⋅ log ⁡ m ) O(\sqrt m + 9 \cdot 2^9 \cdot \log m) O(m +929logm)

最后那个 log ⁡ m \log m logm 是因为: a 1 a_1 a1 的每个质因子数量一定非递减,如果某个质因子在 a i − 1 a i \dfrac{a_{i - 1}}{a_i} aiai1 中出现,说明这个质因子数量在这里至少 − 1 -1 1 了,那么从 a 1 a_1 a1 减少到 a n a_n an,质因子最多就 log ⁡ m \log m logm 个,很快就没了

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;const int INF=0x3f3f3f3f;
const long long INFLL=1e18;typedef long long ll;template<class T>
constexpr T power(T a, ll b){T res = 1;while(b){if(b&1) res = res * a;a = a * a;b >>= 1;}return res;
}constexpr ll mul(ll a,ll b,ll mod){ //快速乘,避免两个long long相乘取模溢出ll res = a * b - ll(1.L * a * b / mod) * mod;res %= mod;if(res < 0) res += mod; //误差return res;
}template<ll P>
struct MLL{ll x;constexpr MLL() = default;constexpr MLL(ll x) : x(norm(x % getMod())) {}static ll Mod;constexpr static ll getMod(){if(P > 0) return P;return Mod;}constexpr static void setMod(int _Mod){Mod = _Mod;}constexpr ll norm(ll x) const{if(x < 0){x += getMod();}if(x >= getMod()){x -= getMod();}return x;}constexpr ll val() const{return x;}explicit constexpr operator ll() const{ return x; //将结构体显示转换为ll类型: ll res = static_cast<ll>(OBJ)}constexpr MLL operator -() const{ //负号,等价于加上ModMLL res;res.x = norm(getMod() - x);return res;}constexpr MLL inv() const{assert(x != 0);return power(*this, getMod() - 2); //用费马小定理求逆}constexpr MLL& operator *= (MLL rhs) & { //& 表示“this”指针不能指向一个临时对象或const对象x = mul(x, rhs.x, getMod()); //该函数只能被一个左值调用return *this;}constexpr MLL& operator += (MLL rhs) & {x = norm(x + rhs.x);return *this;}constexpr MLL& operator -= (MLL rhs) & {x = norm(x - rhs.x);return *this;}constexpr MLL& operator /= (MLL rhs) & {return *this *= rhs.inv();}friend constexpr MLL operator * (MLL lhs, MLL rhs){MLL res = lhs;res *= rhs;return res;}friend constexpr MLL operator + (MLL lhs, MLL rhs){MLL res = lhs;res += rhs;return res;}friend constexpr MLL operator - (MLL lhs, MLL rhs){MLL res = lhs;res -= rhs;return res;}friend constexpr MLL operator / (MLL lhs, MLL rhs){MLL res = lhs;res /= rhs;return res;}friend constexpr std::istream& operator >> (std::istream& is, MLL& a){ll v;is >> v;a = MLL(v);return is;}friend constexpr std::ostream& operator << (std::ostream& os, MLL& a){return os << a.val();}friend constexpr bool operator == (MLL lhs, MLL rhs){return lhs.val() == rhs.val();}friend constexpr bool operator != (MLL lhs, MLL rhs){return lhs.val() != rhs.val();}
};const ll mod = 998244353;
using Z = MLL<mod>;std::vector<int> get_d(int x){std::vector<int> d;for(int i = 2; i * i <= x; ++i){if(x % i ) continue;d.push_back(i);while(x % i == 0) x /= i;}if(x > 1) d.push_back(x);return d;
}void solve(){int n, m;std::cin >> n >> m;std::vector<int> a(n + 1);fore(i, 1, n + 1) std::cin >> a[i];fore(i, 2, n + 1)if(a[i - 1] % a[i]){std::cout << "0\n";return;}auto div = get_d(a[1]);Z ans = 1;fore(i, 2, n + 1){int val = a[i - 1] / a[i];std::vector<int> left;for(auto d : div)if(val % d == 0)left.push_back(d);int sz = left.size();ll K = m / a[i]; //k的上界ll tot = K;fore(mask, 1, 1 << sz){int cnt = __builtin_popcount(mask); //容斥原理系数int now = 1;fore(j, 0, sz)if(mask >> j & 1)now *= left[j];if(cnt & 1) tot -= K / now;else tot += K / now;}ans *= tot;}std::cout << ans << endl;
}int main(){std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);int t;std::cin >> t;while(t--){solve();}return 0;
}

这篇关于Codeforces CodeTON Round 3 D. Count GCD【状压、容斥原理计数】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/878638

相关文章

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

C#中async await异步关键字用法和异步的底层原理全解析

《C#中asyncawait异步关键字用法和异步的底层原理全解析》:本文主要介绍C#中asyncawait异步关键字用法和异步的底层原理全解析,本文给大家介绍的非常详细,对大家的学习或工作具有一... 目录C#异步编程一、异步编程基础二、异步方法的工作原理三、代码示例四、编译后的底层实现五、总结C#异步编程

Go 语言中的select语句详解及工作原理

《Go语言中的select语句详解及工作原理》在Go语言中,select语句是用于处理多个通道(channel)操作的一种控制结构,它类似于switch语句,本文给大家介绍Go语言中的select语... 目录Go 语言中的 select 是做什么的基本功能语法工作原理示例示例 1:监听多个通道示例 2:带

鸿蒙中@State的原理使用详解(HarmonyOS 5)

《鸿蒙中@State的原理使用详解(HarmonyOS5)》@State是HarmonyOSArkTS框架中用于管理组件状态的核心装饰器,其核心作用是实现数据驱动UI的响应式编程模式,本文给大家介绍... 目录一、@State在鸿蒙中是做什么的?二、@Spythontate的基本原理1. 依赖关系的收集2.

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

kotlin中的模块化结构组件及工作原理

《kotlin中的模块化结构组件及工作原理》本文介绍了Kotlin中模块化结构组件,包括ViewModel、LiveData、Room和Navigation的工作原理和基础使用,本文通过实例代码给大家... 目录ViewModel 工作原理LiveData 工作原理Room 工作原理Navigation 工

Java的volatile和sychronized底层实现原理解析

《Java的volatile和sychronized底层实现原理解析》文章详细介绍了Java中的synchronized和volatile关键字的底层实现原理,包括字节码层面、JVM层面的实现细节,以... 目录1. 概览2. Synchronized2.1 字节码层面2.2 JVM层面2.2.1 ente