【每日一题】洛谷 P1621(素数筛选,并查集)

想她一次就背十个单词,当我英语过六级后,我就去告诉她,我很在意她
一天一道数论题,当我可以秒杀数论题的时候,就开始做 DP


今日份快乐:洛谷 P1621 传送门
明天份快乐:洛谷 P3197 传送门


题目大意

有一个 [a, b] 的区间,每个数都是一个独立的集合。你可以进行一个操作,每次你需要选择两个属于不同集合的整数,如果这两个整数拥有大于等于 p 的公共质因数,那么把它们所在的集合合并。
问:最少会留下多少集合?

分析

公共质因数:素数筛选(效率高一点的都可以)
集合合并:并查集(附上大佬讲的并查集视频,传送门
这两个知识点一结合,这个题就完事
(我的做法是先筛选素数再合并集合,有大佬说可以用埃氏筛法,在筛选的同时就可以把集合合并)

本蒟蒻的代码

	
#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;
int num = 0;
int pr[5000];
bool pr_[maxn] = {false};
int f[maxn];

void Init(){      // 欧拉素数筛选法 
 	f[1] = 1;    
 	for(int i = 2; i < maxn; i++){
   		f[i] = i;                  // 顺便初始化 f[i],默认为每个数的祖先都是自己 
  		if(!pr_[i]) pr[num++] = i;
  		for(int j = 0; j < num; j++){
   			if(i * pr[j] > maxn) break;
      			pr_[i * pr[j]] = true;
   			if(i % pr[j] == 0) break;
  		}
 	}
}

int find(int x){           // 寻找 x 的祖先,一个点的祖先是它自己的时候,它就是这个集合的祖先 
  	if(x == f[x]) return x;
   	else return f[x] = find(f[x]);
}

int main(){

	int a, b, p;
 	cin >> a >> b >> p;
 
 	Init();     
 	int ind = 0;
 	while(pr[ind] < p) ind++;    // 寻找第一个大于等于 p 的素数 
	
	int res = b - a + 1;    //刚开始有 b - a + 1 个数,也就是 b - a + 1 个集合
	for(int i = ind; pr[i] <= b; i++){
  		int sum = pr[i];
 		while(sum < a) sum += pr[i];  // 不断加 pr[i],使得 sum 一直是 pr[i] 的倍数,直到 sum 刚好大于等于 a 
 		for(int j = sum; j + pr[i] <= b; j += pr[i]){   // 开始合并 
   			int x = find(j);
   			int y = find(j + pr[i]);
   			if(x != y){       //   j 和 j + pr[i] 一定都是 pr[j] 的倍数,如果这两个点所在的集合的祖先不同,就合并  
    				f[x] = y;
    				res--;    //   每次合并都会少一个集合
   			}
  		}
  	}

 	cout << res << endl;   // 留下的就是答案
 	
    	return 0;
}

大佬的做法


#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 5;
int f[maxn];
bool v[maxn] = {false};

int find(int x){           // 寻找 x 的祖先,一个点的祖先是它自己的时候,它就是这个集合的祖先 
 	if(x == f[x]) return x;
  	else return f[x] = find(f[x]);
}

int main(){
	
	int a, b, p;
	cin >> a >> b >> p;
	
	int res = b - a + 1;   //将答案初始化为a~b间数的个数,每合并一次减1就可以了
	for(int i = a; i <= b; i++) f[i] = i; 		 // 初始化父节点
	
	for(int i = 2; i <= b; i++){   		 //埃氏筛
		if (!v[i]){       // 如果 i 是质数
			if(i >= p) {      //如果当前质数大于p才合并
				for(int j = i * 2; j <= b; j += i){
					v[j] = true;
					//将当前被筛的数与上一个被筛的数合并(第一个被筛的数和质因数本身合并),注意这两个数都要在a~b之间才合并
					if(j - i >= a && find(j) != find(j - i)){ 
						f[find(j)] = find(j - i);
						res--;       // 合并一次,集合少一个
					}
				}
			}
			else{         
    				for(int j = i * 2; j <= b; j += i) v[j] = true;
   			}
   		}
   	}
   	
   	cout << res << endl;
   	
	return 0;
}
 

感觉大佬的写法比较省空间,时间复杂度也要低一点,向大佬学习

坚持的时候很狼狈,等成功以后,丑的还是丑的🤭

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值