【NOIP2017提高A组模拟7.13】好数

题目大意:

我们定义一个非负整数是“好数”,当且仅当它符合以下条件之一:
1.这个数是0或1
2.所有小于这个数且与它互质的正整数可以排成一个等差数列例如,8就是一个好数,因为1,3,5,7排成了等差数列。
给出N个非负整数,然后进行如下三个操作:
1.询问区间[L,R]有多少个好数
2.将区间[L,R]内所有数对S取余(S≤1000000)
3.将第C个数更改为X
这里写图片描述

题解:

首先我们打个表就可以知道好数只有质数、2的幂、0和6这些数,于是我们一个线性筛法就可以求出所有的好数。
接着我们发现除了2操作,都是一个线段树就可以维护的。
2操作怎么办呢?
对于一个数,如果一直用比它大的数去mod它,最多只会mod log2 次,因为一次最多剩下一半。
因此,我们可以在线段树中记录该区间的最大值,如果这个最大值大于mod数,就往下递归,把所有的大于mod数的数mod一遍。
还有单点修改操作,这个操作最多就把一个数拔高一下,重回log次。
所以总共的时间是 O(nlog2)

Code:

#include <cstdio>
#include <cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int N = 1000005;
bool bz[N]; int p[N];

int n, m, num, l, r, s, a[100005];
struct node {
    int s, m;
}t[N * 10];

void update(int i) {
    t[i].m = max(t[i + i].m, t[i + i + 1].m);
    t[i].s = t[i + i].s + t[i + i + 1].s;
}
void insert(int i, int x, int y, int l, int r) {
    if(x == y) {
        a[x] = r;
        t[i].s = !bz[r];
        t[i].m = r;
        return;
    }
    int m = (x + y) / 2;
    if(l <= m) insert(i + i, x, m, l, r); else insert(i + i + 1, m + 1, y, l, r);
    update(i);
}
void change(int i, int x, int y, int l, int r, int mo) {
    if(t[i].m < mo) return;
    if(x == y) {
        a[x] %= mo;
        t[i].s = !bz[a[x]];
        t[i].m = a[x];
        return;
    }
    int m = (x + y) / 2;
    if(r <= m) change(i + i, x, m, l, r, mo); else
    if(l > m) change(i + i + 1, m + 1, y, l, r, mo); else
    change(i + i, x, m, l, m, mo), change(i + i + 1, m + 1, y, m + 1, r, mo);
    update(i);
}
int find(int i, int x, int y, int l, int r) {
    if(x == l && y == r) return t[i].s;
    int m = (x + y) / 2;
    if(r <= m) return find(i + i, x, m, l, r); else
    if(l > m) return find(i + i + 1, m + 1, y, l, r);
    return find(i + i, x, m, l, m) + find(i + i + 1, m + 1, y, m + 1, r);
}

int main() {
    fo(i, 2, 1e6) {
        if(!bz[i]) p[++ p[0]] = i;
        fo(j, 1, p[0]) {
            if(i * p[j] > N) break;
            bz[i * p[j]] = 1;
            if(i % p[j] == 0) break;
        }
    }
    bz[0] = bz[6] = 0;
    int s = 1;
    while(s <= 1e6) {
        bz[s] = 0;
        s *= 2;
    }
    scanf("%d %d", &n, &m);
    fo(i, 1, n) scanf("%d", &a[i]), insert(1, 1, n, i, a[i]);
    fo(i, 1, m) {
        scanf("%d", &num);
        if(num == 1) {
            scanf("%d %d", &l, &r);
            printf("%d\n", find(1, 1, n, l, r));
        }
        if(num == 2) {
            scanf("%d %d %d", &l, &r, &s);
            change(1, 1, n, l, r, s);
        }
        if(num == 3) {
            scanf("%d %d", &l, &r);
            insert(1, 1, n, l, r);
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值