欢迎神犇吊打|

Hanx16Msgr

园龄:2年8个月粉丝:12关注:3

2022-07-30 16:25阅读: 26评论: 0推荐: 0

SP13015 CNTPRIME - Counting Primes

SP13015 CNTPRIME - Counting Primes

SP13015(Luogu)

CNTPRIME - Counting Primes

题目描述

Tortoise and Achilles are playing the Counting the Primes game. Achilles will give Tortoise some numbers, and some intervals, and then Tortoise needs to count the primes on those intervals. It is an easy game, but Tortoise is doing the counting slowly. Achilles is pissed off, so he has given you the task as you are a good programmer. For a twist, he has changed the game a little bit, that is he will give some intervals for counting the prime as well as he will give some intervals to change the numbers in that interval.

You are given an array of n elements. After that you will be given M commands. They are -

  • 0 x y v - you have to change all numbers in the range of x to y (inclusive) to v, where x and y are two indexes of the array.
  • 1 x y - output a line containing a single integer which is the number of primes between x and y (inclusive).

The array is indexed from 1 to n.

Input:

Input starts with an integer T (, denoting the number of test cases.

Each case contains two integers **n (1 and q (1 2*10 4
** ). Then next line, you will be given N integers. After that each of the next q lines will contain a task in one of the following form:

  • 0 x y v (1
  • 1 x y (1

And the numbers will be in range of [2, 10 6 ].

Output:

For each case, print the case number first. Then for each query '1 x y', print the number of primes between x and y [inclusively].

Sample Input

Output for Sample Input

1

5 3

78 2 13 12 3

1 1 2

0 4 4 5

1 1 5

Case 1:

1

4

Note:

  • Use Faster IO like scanf,printf
  • A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. The first prime numbers are 2,3,5,7,11....2,3,5,7,11,…

Solution

首先先要知道题目要求我们做什么。题目给出了一个长为 n 的数列,并且有 q 个操作。有两类操作,一类是区间赋值,另一类是询问区间质数个数。

既然有区间推平的操作,那为什么不用珂朵莉树试试呢?(如果你不会珂朵莉树,可以看我写过的一篇博客,这里面我详细介绍了珂朵莉树这一种数据结构的基础做法)

那么我们先将数列构造成为珂朵莉树,那么区间推平这一操作是珂朵莉树自带的操作,所以只用考虑如何求解区间质数个数这一问题。

珂朵莉树基础操作:

struct Node{
    int l,r;
    mutable int v;
    Node(int l,int r=0,int v=0) : l(l),r(r),v(v) {}
    bool operator< (const Node &a) const{
        return l<a.l;
    }
};
set<Node> ctlt;
auto split(int pos)//分裂区间
{
    auto it=ctlt.lower_bound(Node(pos));
    if (it!=ctlt.end() && it->l==pos) return it;
    it--;
    if (it->r<pos) return ctlt.end();
    int l=it->l,r=it->r,v=it->v;
    ctlt.erase(it);
    ctlt.insert(Node(l,pos-1,v));
    return ctlt.insert(Node(pos,r,v)).first;
}
void assign(int l,int r,int v)//区间推平
{
    auto itr=split(r+1),itl=split(l);
    ctlt.erase(itl,itr);
    ctlt.insert(Node(l,r,v));
}

首先可以用质数筛法预处理出 [1,106] 范围内的质数,这个范围用埃筛就够了,用不上线性筛。那么对于询问的区间 [l,r] ,就可以用类似珂朵莉树的 assign (区间赋值)进行操作,即扫一遍 [l,r] 间的所有区间,如果区间对应的 [l,r,v]v 是质数,那么这一个区间就会对答案产生 rl+1 (即区间长度)的贡献,具体可以参考代码理解:

void prework()//埃筛
{
    prime[1]=1;//1不是质数,所以标记上就行
    for (int i=2;i<=MAXN;i++)
        if (!prime[i])//当前是质数
            for (int j=2;j*i<=MAXN;j++) prime[i*j]=1;//当前质数的所有倍数都是合数
}
int query(int l,int r)
{
    auto itr=split(r+1),itl=split(l);//类似assign操作
    int res=0;//记录[l,r]的区间质数个数
    for (auto it=itl;it!=itr;it++)//遍历之间的所有区间
        if (!prime[it->v]) res+=it->r - it->l +1;//如果这个区间的值v是质数,那么将会对答案产生r-l+1的贡献
    return res;
}

除开这些操作之外还有一点需要注意,就是在每次输出的时候都要记得先输出一个 Case i:i 表示第 i 组数据),我就是因为这个东西检查了半天都没检查出来问题。

Code

#include<bits/stdc++.h>
using namespace std;
template<typename T> void read(T &k)//快读模板
{
    k=0;T flag=1;char b=getchar();
    while (!isdigit(b)) {flag=b=='-'?-1:1;b=getchar();}
    while (isdigit(b)) {k=k*10+b-48;b=getchar();}
    k*=flag;
}
struct Node{
    int l,r;
    mutable int v;
    Node(int l,int r=0,int v=0) : l(l),r(r),v(v) {}
    bool operator< (const Node &a) const{
        return l<a.l;
    }
};
set<Node> ctlt;
auto split(int pos)//分裂区间
{
    auto it=ctlt.lower_bound(Node(pos));
    if (it!=ctlt.end() && it->l==pos) return it;
    it--;
    if (it->r<pos) return ctlt.end();
    int l=it->l,r=it->r,v=it->v;
    ctlt.erase(it);
    ctlt.insert(Node(l,pos-1,v));
    return ctlt.insert(Node(pos,r,v)).first;
}
void assign(int l,int r,int v)//区间推平
{
    auto itr=split(r+1),itl=split(l);
    ctlt.erase(itl,itr);
    ctlt.insert(Node(l,r,v));
}
const int MAXN=1e6;
bool prime[MAXN+5];
int n,q,T;
void prework()//埃筛
{
    prime[1]=1;//1不是质数,所以标记上就行
    for (int i=2;i<=MAXN;i++)
        if (!prime[i])//当前是质数
            for (int j=2;j*i<=MAXN;j++) prime[i*j]=1;//当前质数的所有倍数都是合数
}
int query(int l,int r)
{
    auto itr=split(r+1),itl=split(l);//类似assign操作
    int res=0;//记录[l,r]的区间质数个数
    for (auto it=itl;it!=itr;it++)//遍历之间的所有区间
        if (!prime[it->v]) res+=it->r - it->l +1;//如果这个区间的值v是质数,那么将会对答案产生r-l+1的贡献
    return res;
}
int main()
{
    read(T);
    prework();//筛质数
    for (int t=1;t<=T;t++)
    {
    	printf("Case %d:\n",t);//一定记得要输出这里
        ctlt.clear();//多组数据记得清空珂朵莉树
        read(n),read(q);
        for (int i=1;i<=n;i++)
        {
            int val;read(val);
            ctlt.insert(Node(i,i,val));//每个数看作区间[i,i]的值为val
        }
        for (int i=1;i<=q;i++)
        {
            int op,x,y,v;
            read(op),read(x),read(y);
            if (op==0)
                read(v),assign(x,y,v);//操作0,即推平操作
            else
                printf("%d\n",query(x,y));//操作1,即查询操作
        }
    }
    return 0;
}

需要注意珂朵莉树只能在数据随机的情况下使用,否则一旦 assign 操作过少就会导致区间个数过多,时间就会直接 T 飞。

posted @   Hanx16Msgr  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起