题解及其思路:
数据1e6以内的素数,我们可以先预处理1e6范围内所有数据存储在数组里,这里我们不能用sqrt()这种方式,我们可以选择欧拉线性筛法,选择出所有质数,然后利用二分法找出大于等于L的位置a,和大于R的位置b,在这个范围内求前缀和即可得出结果;
注意不要这个样子找,应该使用二分法:
for (int i = 0; primes[i]<=R; ++i)
{
if(primes[i]>=L)
res += primes[i]* primes[i];
}
当然也可以这个样子:
题意为求L~R区间内质数的平方和。因为有t组数据,需要预处理1~1e6质数平方和的前缀和。预处理时用筛法筛出1~1e6的质数,用数组标记,然后计算前缀和sum[i]。可 O(1)的查询答案为ans=sum[R]-sum[L-1];
上代码:
#include <iostream> #include <cstring> #include <algorithm> #include <map> #include <queue> #include <vector> #include <cmath> #include <set> using namespace std; #define int long long #define endl '\n' typedef pair<int, int> PII; const int N = 1e6 + 10; int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉 void get_primes(int n)//欧拉筛法 { for (int i = 2; i <= n; i++) { if (!st[i]) primes[cnt++] = i; for (int j = 0; primes[j] <= n / i; j++) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } } } void init() { get_primes(N); } void solve() { int l, r; cin >> l >> r; int a = lower_bound(primes, primes + cnt, l) - primes; int b = upper_bound(primes, primes + cnt, r) - primes; int res = 0; for (int i = a; i < b; i++) res += primes[i] * primes[i]; cout << res << endl; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); init(); int T; cin >> T; while (T--) solve(); }
补充知识点:
一:C++-STL中lower_bound与upper_bound的用法:
头文件: #include<algorithm>
函数功能:
1. 函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置
“元素值>查找值”的第一个元素的位置
2.upper_bound(val): 返回容器中第一个值【大于】val的元素的iterator位置
*该函数可以加谓词比较
3.基本使用例子: lower_bound(primes, primes + cnt, l) - primes;
二:欧拉筛(线性筛)及其埃氏筛
解释:
前言:
在最开始的学习中寻找素数一般都是套两层for,这也使得时间复杂度变成O(n),即使加上sqrt效果也不好,然后在数论的学习中,我学到了埃氏筛法,O(nloglogn)的算法,而在一些数据范围达到1e7这样的题目中,也很难让人满意,于是我便学习了欧拉筛法,也即 O(n)的线性筛法
1.埃氏筛
我们先从埃氏筛开始说吧:
基本思想: 从2开始,将每个质数的倍数都标记成合数,以达到筛选素数的目的
int visit[maxn]; void EI_Prime(){ memset(visit,0,sizeof(int)); //初始化都是素数 visit[0] = visit[1] = 1; //0 和 1不是素数 for (int i = 2; i <= maxn; i++) { if (!visit[i]) { //如果i是素数,让i的所有倍数都不是素数 for (int j = i*i; j <= maxn; j += i) { visit[j] = 1; } } }
这里我们可以看到一个小优化吧,j从i*i开始,而不是i+1;因为i*(2--i-1)在2--i-1已经被筛出去了,所以从i*i开始。
缺点:对于一个合数可能被筛选多次,例如 30 = 2 * 15 = 3 * 10 = 5*6……,所以我们可以对它在做一个优化,利用最小质因子来筛选,这便是欧拉线性筛。
2.欧拉线性筛
*先上代码吧:(欧拉筛)
int primes[Max], cnt; // primes[]存储所有素数 bool st[Max]; // st[x]存储x是否被筛掉 void get_primes(int n) { memset(primes, 0, sizeof(primes)); memset(st, 0, sizeof(st)); for (int i = 2; i <= n; i++) { if (!st[i]) primes[cnt++] = i; for (int j = 0; primes[j] <= n / i; j++) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } } }
基本思想 :在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的
3.素数第二定理:
我们都知道素数可以表示成6n-1/6+1的形式,所以有了下面的判断方法,直接判断当前数字是不是素数!!!
bool isprime(int num) { if (num == 2 || num == 3) { return true; } //如果不与6的倍数相邻,肯定不是素数 if (num % 6 != 1 && num % 6 != 5) { return false; } //对6倍邻数进行判断,是否为6倍邻数的倍数 for (int i = 5; i <= sqrt(num); i += 6) { if (num % i == 0 || num % (i + 2) == 0) { return false; } } return true; }
4.快速判断一个数字是不是素数
bool check3(int x)//检查是否为质数 { if (x == 2) return 1; for (int i = 2; i <= sqrt(x); i++) if (x % i == 0) return 0; return 1; }
我们可以发现以下两点
1.合数中的作用是当做倍数的。
2.i%primes[j] == 0 就break的解释:当i是primes[j]的倍数时候,如果继续运算 j+1,i * prime[j+1] = prime[j] * k prime[j+1],这里prime[j]是最小的素因子,当i = k * prime[j+1]时会重复,所以才跳出循环。
eg:i = 8 ,j = 1,prime[j] = 2,如果不跳出循环,prime[j+1] = 3,8 * 3 = 2 * 4 * 3 = 2 * 12,在i = 12时会计算.
三、check函数的写法
二分的 核心代码
bool chk(int t){ int sum=a[1],ans=1; for(int i=2;i<=n;i++){ if(sum+t>a[i]) continue; else{ sum=a[i]; ans++; } } return ans>=m; }