题目相关
题目链接
洛谷,https://www.luogu.com.cn/problem/P1621。
MYOJ,http://47.110.135.197/problem.php?id=5342。
题目描述
Caima 给你了所有 [a,b] 范围内的整数。一开始每个整数都属于各自的集合。每次你需要选择两个属于不同集合的整数,如果这两个整数拥有大于等于 p 的公共质因数,那么把它们所在的集合合并。
重复如上操作,直到没有可以合并的集合为止。
现在 Caima 想知道,最后有多少个集合。
输入格式
一行,共三个整数 a, b, p 用空格隔开。
输出格式
一个数,表示最终集合的个数。
输入样例
10 20 3
输出样例
7
样例解释
对于样例给定的数据,最后有 {10,20,12,15,18},{13},{14},{16},{17},{19},{11} 共 7 个集合,所以输出应该为 7。
数据范围
1 ≤ a ≤ b ≤ 10^5, 2 ≤ p ≤ b。
题解报告
题意分析
题意还是比较清晰的,不需要分析了。
看完后,就知道考察并查集的知识点。
解题思路
写出 p 到 b 的所有素数。可以使用欧拉筛,或者最基本的素数判定方法。
逐一遍历这些素数,找出从 a 到 b 之间的倍数,将这些数放在同一个集合中。
从 a 到 b 遍历,查找集合的个数。
样例数据分析
根据输入样例,我们知道 a=10,b=20,p=3。
初始化并查集
我们使用 STL 的 map 来保存数据,这样并查集的初始值为:
map<int, int> ds;
ds[10] = 10;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 15;
ds[16] = 16;
ds[17] = 17;
ds[18] = 18;
ds[19] = 19;
ds[20] = 20;
p 到 b 的素数
这样,我们可以写出 p 到 b 的素数为:3, 5, 7, 11, 13, 17, 19。下面我们开始遍历素数,合并集合。
3 的倍数
在 [10, 20] 区间中,3 的倍数有:12, 15, 18。将这三个数变成一个集合 {12, 15, 18}。这样并查查集 ds 变成:
ds[10] = 10;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 20;
5 的倍数
在 [10, 20] 区间中,5 的倍数有:10, 15, 20。由于 15 既是 3 的倍数也是 5 的倍数,因此这个集合要上上面的集合合并,成为 {12, 15, 18, 10, 20}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
7 的倍数
在 [10, 20] 区间中,7 的倍数有:14。因此这个集合要上上面的集合合并,成为 {14}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
11 的倍数
在 [10, 20] 区间中,11 的倍数有:11。因此这个集合要上上面的集合合并,成为 {11}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
13 的倍数
在 [10, 20] 区间中,13 的倍数有:13。因此这个集合要上上面的集合合并,成为 {13}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
17 的倍数
在 [10, 20] 区间中,17 的倍数有:17。因此这个集合要上上面的集合合并,成为 {17}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
19 的倍数
在 [10, 20] 区间中,19 的倍数有:19。因此这个集合要上上面的集合合并,成为 {19}。这样并查查集 ds 变成:
ds[10] = 12;
ds[11] = 11;
ds[12] = 12;
ds[13] = 13;
ds[14] = 14;
ds[15] = 12;
ds[16] = 16;
ds[17] = 17;
ds[18] = 12;
ds[19] = 19;
ds[20] = 12;
到这里位置,我们已经完成并集。下面我们开始遍历 [a, b] 查询其中有几个集合。
查询集合数
根据上面的数据,我们知道 ds[i]=i 表示这是一个集合,因此我们可以统计出集合数为:7。
技术细节
可以使用欧拉筛来加速素数判定。
AC 参考代码
//http://47.110.135.197/problem.php?id=5342
//https://www.luogu.com.cn/problem/P1621
//P1621 集合
#include <bits/stdc++.h>
using namespace std;
map<int, int> ds;
map<int, int> ranks;
vector<int> primes;
bool isPrime(int x) {
for (int i=2; i*i<=x; i++) {
if (0==x%i) {
return false;
}
}
return true;
}
int find_root(int x) {
return x==ds[x]?x:ds[x]=find_root(ds[x]);
}
int union_set(int x, int y) {
int x_root=find_root(x);
int y_root=find_root(y);
if (x_root==y_root) {
return 0;
} else {
if (ranks[x_root]>ranks[y_root]) {
ds[y_root]=x_root;
} else if (ranks[x_root]<ranks[y_root]) {
ds[x_root]=y_root;
} else {
ds[x_root]=y_root;
ranks[y_root]++;
}
return 1;
}
}
int main() {
int a,b,p;
cin>>a>>b>>p;
//初始化并查集
for (int i=a; i<=b; i++) {
ds[i]=i;
ranks[i]=0;
}
//列出p到b之间的所有素数
for (int i=p; i<=b; i++) {
if (true==isPrime(i)) {
primes.push_back(i);
}
}
//并集
for (int i=0; i<primes.size(); i++) {
int st=ceil(1.0*a/primes[i])*primes[i];
for (int j=st+primes[i]; j<=b; j+=primes[i]) {
union_set(st, j);
}
}
//查询有几个集合
int ans=0;
for (int i=a; i<=b; i++) {
if (ds[i]==i) {
ans++;
}
}
cout<<ans<<"\n";
return 0;
}