想她一次就背十个单词,当我英语过六级后,我就去告诉她,我很在意她
一天一道数论题,当我可以秒杀数论题的时候,就开始做 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;
}
感觉大佬的写法比较省空间,时间复杂度也要低一点,向大佬学习
坚持的时候很狼狈,等成功以后,丑的还是丑的🤭