线段树区间gcd

小阳的贝壳

题意:
操作 1 l 到 r 的区间加上x
操作2 l到 r 相邻两个差值的绝对值最大 l==r输出0
操作3 l 到 r 的gcd
难点就是是gcd 要想求区间能修改的gcd 你必须要知道一些知识
gcd(a,b)=gcd(a,b-a). gcd(a,b,c)=gcd(a,b-a,c-a) 以此类推
然后只要线段树 维护 相邻两个的差值就可以了 当遇到 操作1
时 由于一个区间的加上相同的数 只有 第一个 和最后一个值改变了因此只有单点修改第一个 与最后一个就行了 即 第一个加上x 最后一个的下一个减去x
代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int n,Q,p[N],h[N];
int add[N];

int gcd(int a,int b){return b?gcd(b,a%b):a;}

struct node{
    int v,g,maxn;
}tree[4*N];

#define lson 2*node
#define rson 2*node+1
#define m (l+r)/2

void build(int l,int r,int node){
    if(l==r){
        tree[node].v=p[l];
        tree[node].g=h[l];
        tree[node].maxn=h[l];
        return;
    }
    build(l,m,lson);
    build(m+1,r,rson);
    tree[node].g=gcd(tree[lson].g,tree[rson].g);
    tree[node].maxn=max(abs(tree[lson].maxn),abs(tree[rson].maxn));
}

void push_down(int node){
    if(add[node]){
        tree[lson].v+=add[node];
        tree[rson].v+=add[node];
        add[lson]+=add[node];
        add[rson]+=add[node];
        add[node]=0;
    }
}

void update(int v,int ql,int qr,int l,int r,int node){
    if(ql<=l&&qr>=r){
        tree[node].v+=v;
        add[node]+=v;
        return;
    }
    push_down(node);
    if(ql<=m)update(v,ql,qr,l,m,lson);
    if(qr>m)update(v,ql,qr,m+1,r,rson);
}

void upgcd(int v,int pos,int l,int r,int node){
    if(l==r){
        tree[node].g+=v;
        tree[node].maxn+=v;
        return;
    }
    if(pos<=m)upgcd(v,pos,l,m,lson);
    else upgcd(v,pos,m+1,r,rson);
    tree[node].g=gcd(tree[lson].g,tree[rson].g);
    tree[node].maxn=max(abs(tree[lson].maxn),abs(tree[rson].maxn));
}

int query(int ql,int qr,int l,int r,int node){
    if(ql<=l&&qr>=r) return tree[node].g;
    int ans=0;
    if(ql<=m)ans=gcd(ans,query(ql,qr,l,m,lson));
    if(qr>m)ans=gcd(ans,query(ql,qr,m+1,r,rson));
    return ans;
}
int querymax(int ql,int qr,int l,int r,int node){
    if(ql>qr)return 0;
    if(ql<=l&&qr>=r){
        return abs(tree[node].maxn);
    }
    int ans=0;
    if(ql<=m)ans=max(ans,querymax(ql,qr,l,m,lson));
    if(qr>m)ans=max(ans,querymax(ql,qr,m+1,r,rson));
    return ans;
}

int queryvalue(int pos,int l,int r,int node){
    if(l==r)return tree[node].v;
    push_down(node);
    if(pos<=m)return queryvalue(pos,l,m,lson);
    return queryvalue(pos,m+1,r,rson);
}


int main(){
    scanf("%d%d",&n,&Q);
    for(int i=1;i<=n;i++){
        scanf("%d",&p[i]);
        h[i]=p[i]-p[i-1];
    }
    build(1,n,1);
    int q,l,r,x;
    while(Q--){
        scanf("%d%d%d",&q,&l,&r);
        if(q==1){
            scanf("%d",&x);
            update(x,l,r,1,n,1);
            upgcd(x,l,1,n,1);
            if(r+1<=n)upgcd(-x,r+1,1,n,1);
        }else if(q==2){
            printf("%d\n",querymax(l+1,r,1,n,1));
        }else if(q==3){
            if(l==r)printf("%d\n",queryvalue(l,1,n,1));
            else{
                int ans=queryvalue(l,1,n,1);
                ans=gcd(ans,query(l+1,r,1,n,1));
                printf("%d\n",abs(ans));
            }
        }
    }
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给定一个长度为 $n$ 的序列 $a_1, a_2, \cdots, a_n$。 定义一个数 $k$ 的权值为 $w_k$,其中 $w_k$ 为 $k$ 的因数中 1 的个数。 定义一个区间 $[l,r]$ 的权值为 $\gcd\{a_l,a_{l+1},\cdots,a_r\}$ 的权值。 现在有 $m$ 次操作,每次操作为将区间 $[l,r]$ 内的数加上 $x$(即 $a_l\gets a_l+x,a_{l+1}\gets a_{l+1}+x,\cdots,a_r\gets a_r+x$)。 对于每次操作,求出操作后 $[1,n]$ 中有多少个区间的权值为素数。 输入格式 第一行包含两个整数 $n,m$。 第二行包含 $n$ 个整数 $a_1,a_2,\cdots,a_n$。 接下来 $m$ 行,每行包含三个整数 $l,r,x$,表示对区间 $[l,r]$ 内的数加上 $x$。 输出格式 对于每次操作,输出操作后 $[1,n]$ 中有多少个区间的权值为素数。 数据范围 $1\le n\le 10^5$, $1\le m\le 10^5$, $1\le a_i,x\le 10^6$ 输入样例: 5 3 1 2 3 4 5 1 5 1 2 3 2 1 3 1 输出样例: 3 4 3 解题思路 注意到一个数的因数中 1 的个数只与其质因数分解后的指数有关,可以预处理出每个质数的指数数组,即 primes[i] 表示第 $i$ 个质数的指数。 对于每个区间 $[l,r]$,求出其权值的质因数分解,即对于每个质数 $p$,求出区间 $[l,r]$ 内 $p$ 的最小指数 $k$,则该区间的权值为 $\prod_{i=1}^np^{k_i}$ 的权值。 然后我们可以把区间加操作看做将区间内所有数乘上 $x+1$,那么区间内每个质数的指数也都加上了 $1$,因此只需要实现一个支持区间乘的数据结构即可。 考虑使用线段树维护区间乘积,对于每个节点,我们可以维护其子节点的质因数分解的指数数组,然后合并子节点时,将指数数组相应位置相加即可。 对于查询区间权值是否为素数,我们可以使用线性筛判定。 时间复杂度 每次操作的时间复杂度为 $O(\log n + k\log n)$,其中 $k$ 为质因数的个数,因此总时间复杂度为 $O(m\log n + kn\log n)$。 C++ 代码 ``` #include <iostream> #include <cstring> #include <algorithm> #include <cmath> using namespace std; const int N = 100010, M = 1000000; int n, m; int w[M + 10]; // w表示每个数的权值 int primes[N], cnt; // primes表示前cnt个质数 int id[M + 10]; // id[i]表示i这个数在primes数组中的位置 int st[N << 2][20]; // st表示线段树节点中每个质数的指数 bool is_prime[M + 10]; // is_prime[i]表示i是否为质数 int res; // res表示答案 void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!is_prime[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) { is_prime[primes[j] * i] = true; if (i % primes[j] == 0) break; } } } void init() { for (int i = 2; i <= M; i ++ ) if (!is_prime[i]) { int t = i, cnt = 0; while (t <= M) w[t] ++, t *= i, cnt ++ ; } get_primes(N - 1); for (int i = 1; i <= cnt; i ++ ) id[primes[i]] = i; } void pushup(int u) { for (int i = 1; i <= cnt; i ++ ) st[u][i] = st[u << 1][i] + st[u << 1 | 1][i]; } void build(int u, int l, int r) { if (l == r) { st[u][id[w[l]]] = 1; return; } int mid = l + r >> 1; build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); pushup(u); } void modify(int u, int l, int r, int ql, int qr, int c) { if (ql <= l && r <= qr) { for (int i = 1; i <= cnt; i ++ ) st[u][i] += c * st[u][i]; return; } int mid = l + r >> 1; if (ql <= mid) modify(u << 1, l, mid, ql, qr, c); if (qr > mid) modify(u << 1 | 1, mid + 1, r, ql, qr, c); pushup(u); } void query(int u, int l, int r, int k) { if (l == r) { bool is_prime = true; for (int i = 2; i <= sqrt(w[l]); i ++ ) if (w[l] % i == 0) { is_prime = false; break; } if (w[l] <= 1) is_prime = false; if (is_prime) res += st[u][k]; return; } int mid = l + r >> 1; query(u << 1, l, mid, k), query(u << 1 | 1, mid + 1, r, k); } int main() { init(); scanf("%d%d", &n, &m); build(1, 1, n); while (m -- ) { int l, r, x; scanf("%d%d%d", &l, &r, &x); modify(1, 1, n, l, r, x); res = 0; query(1, 1, n, x + 1); printf("%d\n", res); } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值