Caima 给你了所有 [a,b][a,b] 范围内的整数。一开始每个整数都属于各自的集合。每次你需要选择两个属于不同集合的整数,如果这两个整数拥有大于等于 pp 的公共质因数,那么把它们所在的集合合并。
重复如上操作,直到没有可以合并的集合为止。
现在 Caima 想知道,最后有多少个集合。
比如整数10 和12先各自为一个集合,如果p为2,就是找到一个大于或等于2的公共质因数,显然10和22都有质因数2, 10和22就能够合并,换回来 10 和11 是否可以合并?10的因素有1 ,2, 5, 10。 11的因素有1,11,这两者没有大于2的公共质因数,10和 11就不能合并了,但是10和22是一个集合,22可以和11合并,因此10,11,22可以在一个集合内。
直观解决方法
找出【a,b】中每个数的质因数,然后与出自己外其他数一一比较,如果存在大于或等于P的公共质因数,就把两个数所在的集合合并,这里可以用并查集合并。如果两个数均在同一集合内,那么不需要合并(因为合并和不合并都一样)。这样遍历一遍之后就能得出最终的不可合并集合个数,计算集合个数得出答案。
例如:存在上面两个集合
怎么合并?
22在领头“10”的集合内
11本来是孤身一人,后面遇到了22,就把22拉入了队伍,“10”的集合里有22,“11”的集合里也有22,这里就可以重复了,判断22的父节点是否相等,不相等就可以合并,
当然也可以把左边加到右边,这里只是简单地说一下思路,还要许多补充的地方。
那么怎么找出质因数,判断两个数是否可以合并呢?按照上述方法,遍历一个数的所有质因数,在和其他数的质因数一一比较显然是不可取的,找到一个数的所有质因数要计算√n次,再次条件下又与其他数的质因数比较,那毫无理由会超时。
提一下素数筛:
就是把合数全部去除了就留下素数了。
那我们先用质数2,后面我们2,3,4把b之前的非质数排除,后面就用3,依次这样做,到4的时候已经被排除了,之后选用5,,选用6的时候在用质数2的时候已经排除了,以此类推……
就这样我们可以直接用b之前的质数进行运算。
接下来就是判断选取的质数是否大于等于p,如果满足条件,则进行集合合并。找【a,b】之间的整数是否满足合并条件。例如,我们最开始是直接选了质数2,接下来我们要22,23,24排除那些合数,以便找到下一个可以直接用的质数,
就有
prime代表质数,judge数组中存储记录哪些是可用质数
接着判断【a,b】之间可合并的数,(前面要初始化,每个数先初始化默认父亲节点是自己)
如果上一个符合条件的数和这个符合条件的数的跟节点不一样,就说明他们还是在两个不同的集合,就可以合并了。
注意:合并是
不是
因为i-prime所要代表的是一个集合,而这个集合可能不仅仅包含了这是这个数
代码如下:
#include
using namespace std;
int root[100005];
bool judge[100005];
int find(int x) {
if (root[x] == x)
return x;
else {
root[x] = find(root[x]);
return root[x];
}
}
int a, b, p, prime,i, j, sum = 0;
int main() {
cin >> a >> b >> p;
for (i = a; i <= b; i++)
root[i] = i;
sum = b-a+ 1;
for (prime = 2; prime <= b; prime++)
{
if (!judge[prime]) {
if (prime >= p) {
for (i = prime * 2; i <= b; i += prime) {
judge[i] = true;
if (i - prime >= a && find(i - prime) != find(i)) {
root[i-prime] = find(i);
sum--;
}
}
}
else {
for (i = prime * 2; i <= b; i += prime) {
judge[i] = true;
}
}
}
}
cout << sum;
}