树状数组与线段树

本文深入探讨了树状数组(也称作 BIT/Fenwick Tree)和线段树这两种数据结构。内容涵盖它们的基本操作如单点修改和区间查询,以及在区间修改、区间求和、区间最大公约数等问题上的应用。文章通过多个实例展示了如何利用树状数组和线段树解决动态维护数组的问题,并提供了详细的代码实现,帮助读者理解和掌握这两种高效的数据结构。
摘要由CSDN通过智能技术生成

1.树状数组

树状数组支持动态维护一个数组,支持单点修改,区间求和,区间修改
本文只给出例题与参考资料
树状数组经典图片
在这里插入图片描述
参考博客
参考视频

楼兰图腾

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200009;
int high[N],low[N];
int n;
int a[N];
int tr[N];
signed lowbit(int x){
    return x&(-x);
}
signed add(int x,int k){
    for(int i=x;i<=n;i+=lowbit(i)){
        tr[i]+=k;
    }
}
signed ask(int x){
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)){
        sum+=tr[i];
    }
    return sum;
}
signed main(){
    cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    for(int i=1;i<=n;i++){
        int x=a[i];
        //统计在区间1~x-1的数的个数,即a[1]~a[i-1]中比a[i]小的数的个数
        low[i]=ask(x-1);
        //统计在区间x+1~n的数的个数,即a[1]~a[i-1]中比a[i]大的数的个数
        high[i]=ask(n)-ask(x);
        add(x,1);  //在x位置上加1代表大小为x的有一个数,建立树状数组
    }
    int res1=0,res2=0;
    memset(tr,0,sizeof tr);
    for(int i=n;i>=1;i--){
        int x=a[i];
        //统计在区间1~x-1的数的个数,即a[i+1]~a[n]中比a[i]小的数的个数
        int low2=ask(x-1);
        //统计在区间x+1~n的数的个数,即a[i+1]~a[n]中比a[i]大的数的个数
        int high2=ask(n)-ask(x);
        add(x,1);
        res1+=high[i]*high2;
        res2+=low[i]*low2;
    }
    cout << res1 << " " << res2;
    return 0;
}

一个简单的整数问题

// 树状数组维护差分数组,区间修改 ,单点查询 ,差分,注意差分的原理应用不要弄混
#include<iostream>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
int tr[N];
int lowbit(int x){
    return x&(-x);
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
void add(int x,int k){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
}
signed main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        cin >> a[i];
    }
    for(int i=1;i<=n;i++){
         add(i,a[i]-a[i-1]);
    }
    while(m--){
        char q;
        cin >> q;
        if(q=='Q'){
            int x;
            cin >> x;//单点查找
            cout << sum(x) << endl;
        }
        else {
            int l,r,c;
            cin >> l >> r >> c;
            add(r+1,-c),add(l,c);
        }
    }
    return 0;
}

一个简单的整数问题2

//区间修改 ,区间求和
//维护两个树状数组 一个维护a[i]的差分 ,一个维护i*a[i]的差分
//对于区间修改 和上题一样 利用差分数组的性质即可
//对于区间求和 我们知道a[i]=b[1]+....+b[i],则前缀和s[i]=a[i]+...+a[i]=i*b[1]+(i-1)*b[2]+...+b[i]
/// 可以化为s[i]=(i+1)(b[1]+b[2]+..b[i])-(1*b[1]+2*b[2]+...+i*b[i]);
#include<iostream>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
int a[N];
int tr1[N],tr2[N];
int lowbit(int x){
    return x&(-x);
}
void add(int tr[],int x,int k){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
}
int sum(int tr[],int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
signed main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        add(tr1,i,a[i]-a[i-1]);
        add(tr2,i,i*a[i]-i*a[i-1]);
    }
    while(m--){
        char q;
        cin >> q;
        if(q=='Q'){ //求区间和
            int l,r;
            cin >> l >> r;
            cout << ((r+1)*sum(tr1,r)-sum(tr2,r))-(l*sum(tr1,l-1)-sum(tr2,l-1))<<endl;
        }
        else { //区间修改
            int l,r,c;
            cin >> l >> r >> c;
            add(tr1,r+1,-c),add(tr1,l,c);
            add(tr2,r+1,-c*(r+1)),add(tr2,l,c*l);
        }
    }
    return 0;
}

谜一样的牛

//我们从后往前看,假设最后一个数为x,代表前面有x个比他小的数,则在第X这个
//位置上 应该是第X+1小的数 我们可以利用二分求出来这个数,因为每个数只出现
//一次所以用完一个数后要给这个数置0,a[x]=1 表示 X这个数还能用,
//sum[x]=k 表示前X个数其中有k个还没用过,我们二分的就应该是SUM数组,
//假设第i头 前面有q个比他大的数那么我们应该从SUM中找出大于等于q+1的最小值
//那么这头牛的高度就应该为二分出来的那个下标+1的数
//比如 当前牛前面有6个比他低的 ,我们二分出来 sum[9]=7,表示前9个数有7个
//还没用过,这就说明了前面的牛必全是前8个数里面的,当前牛的高度必为9
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n;
int a[N];
int h[N];
int tr[N];
int lowbit(int x){
    return x&(-x);
}
void add(int x,int c){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
int main(){
    cin >> n;
    for(int i=2;i<=n;i++){
        cin >> a[i];
        add(i,1);
    }
    a[1]=0;
    add(1,1);
    for(int i=n;i>=1;i--){
      //  int k=a[i]+1;
        int l=1,r=n;
        while(l<r){
            int mid=l+r>>1;
            if(sum(mid)>=a[i]+1){
                r=mid;
            }
            else {
                l=mid+1;
            }
        }
        h[i]=r;
        add(r,-1);
    }
    for(int i=1;i<=n;i++) {
        cout << h[i] << endl;
    }
    return 0;
}

逆序对

//对于每一个数统计一下这个数后面有多少个数比他小,离散化加树状数组
#include<iostream>
#include<unordered_map>
#include<algorithm>
#define int long long
using namespace std;
const int N=5e5+10;
int a[N],lsh[N];
int n,m;
int tr[N];
int lowbit(int x){
    return x&(-x);
}
void add(int x,int k){
    for(int i=x;i<=N;i+=lowbit(i)) tr[i]+=k;   //注意啊这里的循环终止条件是N 他应该是树状数组的最大下标   
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
signed main(){
    cin >> n;
    int res=0;
    for(int i=1;i<=n;i++) cin >> a[i],lsh[i]=a[i];
    sort(lsh+1 , lsh+n+1);
    int cnt = unique(lsh+1 , lsh+n+1) - lsh - 1;//unique会删除相邻的重复的元素,其实
    //并不是真的删除而是把这些元素放到数组的最后面了,这里的cnt就是去重后数组的长度
    for(int i=1; i<=n; i++){
	    a[i] = lower_bound(lsh+1 , lsh+cnt+1 , a[i]) - lsh;//lower_bound返回的是大于等于a[i]的第一个数
	    //的位置 因为是位置 所以要减去lsh
    }
    //至此离散化结束
    for(int i=n;i>=1;i--){
        res+=sum(a[i]-1);
        add(a[i],1);
    }
    cout << res;
    return 0;
}

关于离散化
在这里插入图片描述
这里还有一个关于离散化的例题
区间和

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N=3e5+10;
int n,m;
int a[N],lsh[N],sum[N],num[N];
pair<int,int>v[N];
pair<int,int>p[N];
int main(){
    cin >>n >>m;
    int cnt1=1,cnt2=1,cnt3=1;
    for(int i=1;i<=n;i++){
        int x,c;
        cin >>x >> c;
        a[cnt1++]=x;
        lsh[cnt3++]=x;
        p[i]={x,c};
    }
    for(int i=1;i<=m;i++){
        int l,r;
        cin >> l >> r;
        v[i]={l,r};
        a[cnt1++]=l;
        a[cnt1++]=r;
        lsh[cnt3++]=l;
        lsh[cnt3++]=r;
    }
    //               for(int i=1;i<cnt1;i++){
    //     cout << a[i] << " ";
    // }
    // cout << endl;
    sort(a+1,a+cnt1);
    //           for(int i=1;i<cnt1;i++){
    //     cout << a[i] << " ";
    // }
    // cout << endl;
    int cnt=unique(a+1,a+cnt1)-1-a;
    //     for(int i=1;i<cnt1;i++){
    //     cout << a[i] << " ";
    // }
    // cout << endl;
    for(int i=1;i<cnt1;i++){
        lsh[i]=lower_bound(a+1,a+cnt+1,lsh[i])-a;
    }
    // for(int i=1;i<cnt1;i++){
    // //     cout << a[i] << " ";
    //   cout << lsh[i] << endl;
    //  }
    for(int i=1;i<=n;i++){
        num[lsh[i]]+=p[i].y;
       
    }
    for(int i=1;i<cnt1;i++){
        sum[i]=sum[i-1]+num[i];
    //    cout << num[i] << endl;
    }
    for(int i=1;i<=m;i++){
        int l=lower_bound(a+1,a+cnt+1,v[i].x)-a;
        int r=lower_bound(a+1,a+cnt+1,v[i].y)-a;
        // cout << v[i].x << " " << v[i].y << " ";
        // cout << l << " " << r << endl;
        cout << sum[r]-sum[l-1]<<endl;
    }
    return 0;
}

火柴人排队

//有一点数论 就是比如说a>b>c,e>d>f 那么从这6个数中两个数为一对相乘再相加,
//那么一定是 a*e+b*d+c*f 最大
//我们平常进行离散化 离散化得到新数组 假如a[5]=7,代表第5个位置上放的是第7小的
//但是本题离散化后应该要求得到 a[5]=7 第5小的数放在第7个位置上
//这个题要求我们的是 要把第一个数组第x位置上应该放第n小的 ,第二个数组第x个位置
//上也应该放第n小的,我们为了实现这一要求,新建数组q[a[i]]=b[j] ,代表当前a数组第i小的
//数的位置对应的是b数组第j小的位置,比如q[5]=7,代表了当前a数组第5小的位置对应的是
//b数组第7小的位置,我们想让a数组第5小的位置对应的是b数组第5小的位置
//即我们想让a[i]==b[j],即我们要让q数组满足一一对应,q[1]=1,q[2]=2 ......
//所以我们让q数组升序 再求逆序对即可
#include<iostream>
#include<unordered_map>
#include<algorithm>
#define int long long
using namespace std;
const int N=5e5+10,mod=1e8-3;
struct Node {
    int num,id;
}a[N],b[N];
int n,m;
int tr[N],q[N];
int lowbit(int x){
    return x&(-x);
}
void add(int x,int k){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k; 
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
bool cmp(Node x, Node y)
{
    return x.num < y.num;
}
signed main(){
    cin >> n;
    int res=0;
    for(int i=1;i<=n;i++) cin >> a[i].num,a[i].id=i;
    for(int i=1;i<=n;i++) cin >> b[i].num,b[i].id=i;
    sort(a+1,a+n+1,cmp);// 结构体排序必须得写一个cmp?
    sort(b+1,b+n+1,cmp);
    for(int i=1;i<=n;i++){
        q[a[i].id]=b[i].id;
    }
    for(int i=n;i>=1;i--){
        res=(res+sum(q[i]-1))%mod; // sum(6) 0  sum(9) 
        add(q[i],1);  //add(7,1)    
    }
    cout << res;
    return 0;
}

配对统计

// 关于本题 有以下几点提示或说明
//1.sort结构体时要自己写一个cmp不然会报错
//2.注意预处理的思想,本题充分利用了预处理思想,预处理了所有可能出现的好对
//3.本题注意定义的结构体abc,要带一个id本题都是利用每个数组的编号取进行维护,而不是数组本身
//数组本身进行了排序之后已经失去了它本身的意义,所以要保留编号,而且询问时询问
//的也是编号
//具体思路见链接,有点像贪心里的区间问题?
#include<iostream>
#include<vector>
#include<algorithm>
#define x first
#define y second
#define int long long
using namespace std;
const int N=3e5+10;
int n,m;
int tr[N];
vector<pair<int,int>>v;
struct abc{
    int num,id;
}a[N];
struct Node{
    int l,r,id;
}quesiton[N];
bool cmp(Node x,Node y){
    if(x.r!=y.r) return x.r<y.r;
    return x.l<y.l;
}
int lowbit(int x){
    return x&-x;
}
void add(int x,int k){
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;
}
int sum(int x){
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
bool cmpp(pair<int,int>a,pair<int,int>b){
    if(a.y!=b.y) return a.y<b.y;
    return a.x<b.x;
}
bool cmppp(abc a1,abc a2){
    return a1.num<a2.num;
}
void wuhu(abc a1,abc a2){
    int l=min(a1.id,a2.id);
    int r=max(a1.id,a2.id);
    v.push_back({l,r});
}
signed main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> a[i].num,a[i].id=i;
    if(n==1) {  //针对被hack的一个数据 特判一下
        cout << 0;
        return 0;
    }
    //预处理所有好对
    sort(a+1,a+n+1,cmppp);
    wuhu(a[1],a[2]);
    wuhu(a[n-1],a[n]);
    for(int i=2;i<n;i++){
        int dis1=abs(a[i].num-a[i-1].num),dis2=abs(a[i].num-a[i+1].num);
        if(dis1<dis2) wuhu(a[i-1],a[i]);
        else if(dis2==dis1) wuhu(a[i-1],a[i]),wuhu(a[i],a[i+1]);
        else wuhu(a[i],a[i+1]);
    }
    sort(v.begin(),v.end(),cmpp);
    //预处理完毕
    //预处理 所有询问,并将所有询问按右端点进行从小到大排序 
    for(int i=1;i<=m;i++){
        int l,r;
        cin >> l >> r;
        quesiton[i]={l,r,i};
    }
    sort(quesiton+1,quesiton+1+m,cmp);
    //预处理完毕 开始询问        
    int ans=0;
    for(int i=1,j=0;i<=m;i++){
        while(v[j].y<=quesiton[i].r&&j<v.size()) add(v[j].x,1),j++; // 加入好对
        ans+=quesiton[i].id*(sum(quesiton[i].r)-sum(quesiton[i].l-1));
    }
    cout << ans;
    return 0;
}

在这里插入图片描述
参考题解

2.线段树

参考讲解

最大数

//线段数 基础四个操作:建树,单点修改,区间查询,pushup
#include<iostream>
#define int long long
using namespace std;
const int N=2e5+10;
int n,m,p,a;
struct Node{
    int l,r,maxv;
}tr[N*4];
void build(int u,int l,int r){
    tr[u]={l,r,0};
    if(l==r) return;
    int mid=l+r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
}
void modify(int u,int k,int c){
    if(tr[u].l==k&&tr[u].r==k){
        tr[u].maxv=c;
        return;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(mid>=k){
        modify(u<<1,k,c);
    }
    else{
        modify(u<<1|1,k,c);
    }
    tr[u].maxv=max(tr[u<<1].maxv,tr[u<<1|1].maxv);
}
int query(int u,int l,int r){
    int ans=0;
    if(l<=tr[u].l&&r>=tr[u].r){
        ans=max(ans,tr[u].maxv);
        return ans;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(mid>=l){
        ans=max(ans,query(u<<1,l,r));
    }
    if(mid<r){       // 注意这里一定不要再带=号了 一定要与建树的时候保持一致
    //建树的时候 把mid分给了左半边,如果这里再加等号,就回去右半边去找mid,很显然找不到会死循环
        ans=max(ans,query(u<<1|1,l,r));
    }
    return ans;
}
signed main(){
    cin >> m >> p;
    build(1,1,m);
    while(m--){
        char c;
        int x;
        cin >> c >> x;
        if(c=='A'){
            modify(1,n+1,(x+a)%p);
           // cout << "tmp" << n<<endl;
            n++;
        }
        else {
            a=query(1,n-x+1,n);
            cout << a << endl;
        }
    }
    return 0;
}

你能回答这些问题吗

// yes  i  can 
//单点修改,区间查询
#include<iostream>
using namespace std;
const int N=5e5+10;
int n,m;
int a[N];
struct Node{
    int l,r,trm,sum,suml,sumr; // 左边界,右边界,最大连续和,区间和,前缀和,后缀和
}tr[N*4];
void pushup(Node &u,Node &sonl,Node &sonr){
    u.sum=sonl.sum+sonr.sum;
    u.suml=max(sonl.suml,sonl.sum+sonr.suml);
    u.sumr=max(sonr.sumr,sonr.sum+sonl.sumr);
    u.trm=max(max(sonl.trm,sonr.trm),sonl.sumr+sonr.suml);
}
void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r) {
        tr[u]={l,r,a[r],a[r],a[r],a[r]}; // 叶子节点
        return;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    
    pushup(u);
}
Node query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r){
        return tr[u];
    }
    Node res;
    int mid=tr[u].l+tr[u].r>>1;
    if(r<=mid){
        return query(u<<1,l,r);
    }
    else if(l>mid){
        return query(u<<1|1,l,r);
    }
    Node sonl=query(u<<1,l,r);
    Node sonr=query(u<<1|1,l,r);
    pushup(res,sonl,sonr);
    return res;
}
// 当然query也可以这样写 但因为本题返回的是Node 所以按上面的写比较方便
// Node query(int u,int l,int r){
//     if(tr[u].l>=l&&tr[u].r<=r){
//         return tr[u];
//     }
//     Node res,sonl,sonr;
//     int mid=tr[u].l+tr[u].r>>1;
//     if(l<=mid&&r<=mid){
//         return query(u<<1,l,r);
//     }
//     else if(r>mid&&l>mid){
//          return query(u<<1|1,l,r);
//     }
//     else {
//         sonl=query(u<<1,l,r);
//         sonr=query(u<<1|1,l,r);
//         pushup(res,sonl,sonr);
//     }
//     return res;
//     // 如果要这样写 
//     // if(l<=mid) sonl=query(u<<1,l,r);
//     // if(r>mid)  sonr=query(u<<1|1,l,r);
//     // pushup(res,sonl,sonr);
//     // return res;
//     // 这样写显然是错误的,因为有可能sonl或者sonr为空
// }
void modify(int u,int k,int c){
    if(tr[u].l==k&&tr[u].r==k){
        tr[u]={k,k,c,c,c,c};
        return;
    }
    int mid=tr[u].l+tr[u].r>>1;
    if(k<=mid){
        modify(u<<1,k,c);
    }
    else {
        modify(u<<1|1,k,c);
    }
    pushup(u);
}
int main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> a[i];
    build(1,1,n);
    while(m--){
        int k,x,y;
        cin >> k >> x >> y;
        if(k==1){
            if(x>y) swap(x,y);
            cout<<query(1,x,y).trm<<endl;
        }
        else {
            modify(1,x,y);
        }
    }
    return 0;
}

区间最大公约数

//利用线段树维护数组a的差分数组 利用已知数学结论 gcd(a,b,c)=gcd(a,b-c,c-b);
//则区间(l,r)的最大公约数为 gcd(a[l],a[l+1],a[l+2]....a[r])==
//gcd(a[l],a[l+1]-a[l],a[l+2]-a[l+1]...a[r]-a[r-1])=gcd(g[l],gcd(b[l+1],b[l+2]..b[r]))

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10;
struct Node{
    int l,r,sum,g;  // 左边界 有边界 区间和 区间最大公约数 
}tr[N*4];
int n,m;
int a[N];
void pushup(Node &u,Node &sonl,Node &sonr){
    u.sum=sonl.sum+sonr.sum;
    u.g=__gcd(sonr.g,sonl.g);
}
void pushup(int u){
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r){
        int tmp=a[r]-a[r-1];
        tr[u]={l,r,tmp,tmp};
        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 x,int k){
    if(tr[u].l==x&&tr[u].r==x){
        int tmp=tr[u].sum+k;
        tr[u]={x,x,tmp,tmp};
        return;
    }
    int mid=tr[u].l+tr[u].r >> 1;
    if(x<=mid) modify(u<<1,x,k);
    else {
        modify(u<<1|1,x,k);
    }
    pushup(u);
}
Node query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r){
        return tr[u];
    }
    int mid=tr[u].r+tr[u].l >> 1;
    Node res;
    if(r<=mid) res=query(u<<1,l,r);
    else if(l>mid) res=query(u<<1|1,l,r);
    else {
        Node sonl=query(u<<1,l,r);
        Node sonr=query(u<<1|1,l,r);
        pushup(res,sonl,sonr);
    }
    return res;
}
signed main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> a[i];
    build(1,1,n);
  
    while(m--){
        char choice;
        cin >> choice;
        if(choice=='Q'){// 询问区间x->y的最大公约数
            int x,y;
            cin >> x >> y;
            int l=query(1,1,x).sum;
            int r=0;
            if(x+1<=y) {
                r=query(1,x+1,y).g;}
            int ans=__gcd(l,r);
            cout << abs(ans) <<endl;
        }
        else{
            int l,r,k; //将区间(l,r)加上k
            cin >> l >> r >> k;
            modify(1,l,k);
            if(r+1<=n) modify(1,r+1,-k);
        }
    }
    return 0;
}

下面是带懒标记,懒标记可以支持区间修改,不带懒标记只能进行单点修改

一个简单的整数问题2

//线段树 如果不用懒标记 只支持做单点修改
#include<iostream>
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,m;
struct Node{
    int l,r,sum,add;
}tr[N*4];
void pushup(int u){
    tr[u].sum=tr[u << 1].sum+tr[u <<1 | 1 ].sum;  //一定要搞清楚实际含义,着他们
    //这里多了一个等号 调了一万年
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r){
        tr[u]={l,r,a[l],0};
        return;
    }
    else {
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void pushdown(int u){
    auto &root=tr[u],&l=tr[u<<1],&r=tr[u<<1|1];
    l.sum+=(l.r-l.l+1)*root.add,l.add+=root.add;
    r.sum+=(r.r-r.l+1)*root.add,r.add+=root.add;
    root.add=0;
}

int query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r){
        return tr[u].sum;
    }
    int tmp=0;
    int mid=tr[u].l+tr[u].r>>1;
      //查询区间含不住当前区间
    pushdown(u); // 下放!
    if(l<=mid) tmp+=query(u<<1,l,r);
    if(r>mid) tmp+=query(u<<1|1,l,r);
    return tmp;
}
void modify(int u,int l,int r,int k){
    if(tr[u].l>=l&&tr[u].r<=r){ // 修改区间含的住当前区间,当前区间值都要改捏
        tr[u].sum+=(tr[u].r-tr[u].l+1)*k;//因为我们做的懒标记是不包括根节点的,所以这里应该先给
        //根节点加上 ,然后再操作该节点的懒标记
        tr[u].add+=k;
        return;
    }
    //含不住 下放,因为modify里面有个pushup操作,如果懒标记没有下传就会导致子节点的值没有第一时间更新,父节点就会被错误的更新
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid) modify(u<<1,l,r,k);
    if(r>mid) modify(u<<1|1,l,r,k);
    pushup(u);
}
signed main(){
    cin >> n >> m;
    for(int i=1;i<=n;i++) cin >> a[i];
    build(1,1,n);
    while(m--){
        char c;
        cin >> c;
        if(c=='Q'){//询问区间和
            int l,r;
            cin >> l >> r;
            cout<< query(1,l,r) << endl;
        }
        else {     // 区间l->r 加上c
            int l,r,c;
            cin >> l >> r >> c;
            modify(1,l,r,c);
        }
    }
    return 0;
}

维护序列

#include<iostream>
#define int long long
using namespace std;
const int N=1e5+10;
struct Node {
    int l,r,sum,mul,add; //  左边界 有边界 区间和 乘懒标记 加懒标记 
}tr[N*4];
int a[N];
int n,m,p;
void excute(Node &u,int mul,int add){  //表示当前对tr[u]执行乘mul加add操作
    u.sum=(u.sum*mul+add*(u.r-u.l+1))%p;//我们的懒标记只是存的子节点的操作,对于本层节点直接执行
    u.mul=(u.mul*mul)%p;//给节点的乘懒标记进行乘法
    u.add=(u.add*mul+add)%p;//给节点的加懒标记进行乘法 并 进行加法
}
void pushdown(int u){
    excute(tr[u<<1],tr[u].mul,tr[u].add);
    excute(tr[u<<1|1],tr[u].mul,tr[u].add);
    tr[u].mul=1,tr[u].add=0;
}
void pushup(int u){
    tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r){
        tr[u]={l,r,a[l],1,0};
    }
    else {
        tr[u] = {l, r, 0, 1, 0};
        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 mul,int add){
    if(tr[u].l>=l&&tr[u].r<=r){
        excute(tr[u],mul,add);
    }
    else {
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,mul,add);
        if(r>mid) modify(u<<1|1,l,r,mul,add);
        pushup(u);
    }
}
int query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r){
        return tr[u].sum;
    }
    else {
        pushdown(u);
        int ans=0;
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) ans=query(u<<1,l,r);
        if(r>mid) ans=(ans+query(u<<1|1,l,r))%p;
        return ans;
    }
}
signed main(){
    cin >> n >> p;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    cin >> m;
    while(m--){
        int c,l,r;
        cin >> c >> l >> r;
        if(c==1){ // 区间乘
            int k;
            cin >> k;
            modify(1,l,r,k,0);
        }
        else if(c==2){  // 区间加
            int k;
            cin >> k;
            modify(1,l,r,1,k);
        }
        else{  //区间和
            cout << query(1,l,r) << endl;
        }
    }
    return 0;
}

亚特兰蒂斯

//扫描线  且不同pushdown
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct segment{
    double x,y1,y2;
    int k;
}sem[N*2];
struct Node{
    int l,r,cnt;  // 左边界 有边界,当前节点被覆盖的次数 ,当前节点被覆盖的长度
    double len;
}tr[N*8];
int n,m;
double lsh[N];
bool cmp(segment a,segment b){
    return a.x<b.x;
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r){
        return;
    }
    else {
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
    }
}
void pushup(int u){   // 重点理解 ,由子节点更新父节点
    if(tr[u].cnt>0) tr[u].len=lsh[tr[u].r+1+1]-lsh[tr[u].l+1];// 如果当前区间已经被覆盖过 直接返回即可 ,因为离散化问题所以存在+1问题
    else if(tr[u].l == tr[u].r) tr[u].len = 0;//如果当前是叶子节点,而且没被覆盖过,则它当然为0
    else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;//如果当前区间没被覆盖过,且当前区间不是叶子节点,则它的有效覆盖长度为它的两个儿子相加
}
void modify(int u,int l,int r,int k){
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].cnt+=k;
        pushup(u);//这里由pushup 是因为叶节点的len需要更新
    }
    else {
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,k);
        if(r>mid) modify(u<<1|1,l,r,k);
        pushup(u);
    }
}
int tmp;int j=0;
int get(double x){
    return lower_bound(lsh+1 , lsh+j+1 ,x) - lsh-1;
}
int main(){
    int t=0;
    while(cin >> n,n){
    j=0,tmp=0,m=0;
    for(int i=0;i<n;i++){
        double x1,x2,y1,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        sem[m++]={x1,y1,y2,1};
        sem[m++]={x2,y1,y2,-1};
        lsh[++j]=y1,lsh[++j]=y2;
    }
    sort(lsh+1,lsh+1+j);
    tmp=unique(lsh+1,lsh+j+1)-lsh-1;
    sort(sem,sem+m,cmp);
    build(1,0,j);
    double res=0;
    for(int i=0;i<m;i++){
        if(i>0) res+=tr[1].len*(sem[i].x-sem[i-1].x);
        modify(1,get(sem[i].y1),get(sem[i].y2)-1,sem[i].k);//注意理解区间与点 这里要-1
    }
 
   cout << "Test case #" << ++t << endl;
    cout <<"Total explored area: ";
        printf("%.2lf\n\n",res);
    }
    return 0;
}

在这里插入图片描述

油漆面积

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e4+10;
struct segment{
    int x,y1,y2,k;  
}sem[N*2];
struct Node{
    int l,r,cnt,len;  // 左边界 有边界,当前节点被覆盖的次数 ,当前节点被覆盖的长度
}tr[N*4];
int n,m;
bool cmp(segment a,segment b){
    return a.x<b.x;
}
void build(int u,int l,int r){
    tr[u]={l,r};
    if(l==r){
        return;
    }
    else {
        int mid=l+r>>1;
        build(u<<1,l,mid);
        build(u<<1|1,mid+1,r);
    }
}
void pushup(int u){   // 重点理解 ,由子节点更新父节点
    if(tr[u].cnt>0) tr[u].len=tr[u].r-tr[u].l+1;// 如果当前区间已经被覆盖过 直接返回即可
    else if(tr[u].l == tr[u].r) tr[u].len = 0;//如果当前是叶子节点,而且没被覆盖过,则它当然为0
    else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;//如果当前区间没被覆盖过,且当前区间不是叶子节点,则它的有效覆盖长度为它的两个儿子相加
}
void modify(int u,int l,int r,int k){
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].cnt+=k;
        pushup(u);
    }
    else {
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,k);
        if(r>mid) modify(u<<1|1,l,r,k);
        pushup(u);
    }
}
int main(){
    cin >> n;
    for(int i=0;i<n;i++){
        int x1,x2,y1,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        sem[m++]={x1,y1,y2,1};
        sem[m++]={x2,y1,y2,-1};
    }
    sort(sem,sem+m,cmp);
    build(1,0,10000);
    int res=0;
    for(int i=0;i<m;i++){
        if(i>0) res+=tr[1].len*(sem[i].x-sem[i-1].x);
        modify(1,sem[i].y1,sem[i].y2-1,sem[i].k);
    }
    cout << res;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值