基础训练赛第八场--26403--B题(素数第二定理,二分,欧拉筛(线性筛),前缀和)


 题解及其思路:

        数据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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值