[luogu p1621] 集合
集合
题目描述
Caima 给你了所有 \([a,b]\) 范围内的整数。一开始每个整数都属于各自的集合。每次你需要选择两个属于不同集合的整数,如果这两个整数拥有大于等于 \(p\) 的公共质因数,那么把它们所在的集合合并。
重复如上操作,直到没有可以合并的集合为止。
现在 Caima 想知道,最后有多少个集合。
输入输出格式
输入格式
一行,共三个整数 \(a,b,p\),用空格隔开。
输出格式
一个数,表示最终集合的个数。
输入输出样例
输入样例 #1
10 20 3
输出样例 #1
7
说明
样例 1 解释
对于样例给定的数据,最后有 \(\{10,20,12,15,18\},\{13\},\{14\},\{16\},\{17\},\{19\},\{11\}\) 共 \(7\) 个集合,所以输出应该为 \(7\)。
数据规模与约定
- 对于 \(80\%\) 的数据,\(1 \leq a \leq b \leq 10^3\)。
- 对于 \(100\%\) 的数据,\(1 \leq a \leq b \leq 10^5,2 \leq p \leq b\)。
分析
这题的题目都写了大字集合了,疯狂暗示我们用并查集做啊。
那么具体怎么做呢?
首先我们先用欧拉筛筛出一个质数表,然后在质数表中选择 \(\ge p\) 的质数(我们假定其为 \(P\)),用一个循环变量 \(j\) 枚举,向上乘,直到 \(j \times P > b\) 时停止,过程中不断将 \(j \times P\) 和 \(j\) 合并(也就是把 \(P\) 的 \(j\) 倍数字全部合并到一个并查集),最后数 \(a \sim b\) 中有多少个集合即可,即:满足 \(fa_i = i\) 且 \(a \le i \le b\) 的数有多少个。
上代码啦。
代码
/*
* @Author: crab-in-the-northeast
* @Date: 2020-08-19 00:27:02
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-08-19 01:13:48
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
const int maxb = 100005;
bool isprime[maxb];
int prime_num[maxb];
int fa[maxb];
void prime(int n) {//预处理 1 ~ n 的所有质数
std :: memset(isprime, true, sizeof(isprime));
isprime[1] = false;
for (int i = 2; i <= n; ++i) {
if (isprime[i]) prime_num[++prime_num[0]] = i;
for (int j = 1; j <= prime_num[0] && i * prime_num[j] <= n; ++j) {
isprime[i * prime_num[j]] = false;
if (i % prime_num[j] == 0) break;
}
}
}
int find(int x) {
while (x != fa[x]) x = fa[x] = fa[fa[x]];
return x;
}
int main() {
int a, b, p;
std :: scanf("%d%d%d", &a, &b, &p);
prime(b);
for (int i = 1; i <= b; ++i) {
fa[i] = i;
}
for (int i = 1; i <= prime_num[0]; ++i) {
if (prime_num[i] >= p) {
for (int j = std :: ceil(a * 1.0 / prime_num[i]); j * prime_num[i] <= b; ++j) {
int fax = find(prime_num[i]);
int fay = find(j * prime_num[i]);
fa[fax] = fay;//合并所有prime_num[i] 的 j 倍数字。
}
}
}
int ans = 0;
for (int i = a; i <= b; ++i)
if (fa[i] == i)
++ans;//统计集合数量
std :: printf("%d\n", ans);
return 0;
}