【学习笔记】进阶算法——树状数组 & 线段树

树状数组

单点修改,区间查询。

例题。

核心在于 lowbit

总之,就是用一个 \(c\) 数组去存储。\(c_i\) 的定义?含有 lowbit!(懒得说,反正定义也不重要

看一下最重要的两个函数:

//两个函数的时间复杂度都是 log 级别的
//其中的 x&-x 这一部分其实上就是所谓的 lowbit
void upd(int x,int k){while(x<=n)c[x]+=k,x+=x&-x;return;}
//upd(x,k) 表示对 x 这个点的数值加上 k
int ask(int x){int as=0;while(x)as+=c[x],x-=x&-x;return as;}
//ask(x) 返回的是 1~x 这个区间的所有数的和
//如果需要算 l~r 这个区间的和可以使用 ask(r)-ask(l-1)

然后就结束了。

很简单对吧,但是树状数组的运用很广。

这里放一个习题

这个题很好玩的。可以用线段树做,这点我不否认,但更强的法子是用树状数组。而且它维护的是一个差分数组!也就是说,你查询 ask(x),就是查到了第 \(x\) 个数的真实数值!是不是超强的?毕竟树状数组代码短,常数小,而线段树嘛……难写又难调,代码不简洁,常数还大得吓人呢!

说了这么多,还是贴一下这个习题的代码吧:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5+5;LL n,m,c[N];
void upd(int x,LL k){while(x<=n)c[x]+=k,x+=x&-x;return;}
LL ask(int x){LL as=0;while(x)as+=c[x],x-=x&-x;return as;}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){int x;cin>>x;upd(i,x),upd(i+1,-x);}
    while(m--){
        LL u;cin>>u;u++;LL x=ask(u);
        upd(u,-x),upd(u+1,x);upd(1,x/n),upd(n+1,-x/n);
        upd(u+1,1),upd(min(n,u+x%n)+1,-1);
        if(u+x%n>n)upd(1,1),upd(u+x%n-n+1,-1);
    }
    for(int i=1;i<=n;i++)cout<<ask(i)<<" ";
    return 0;
}

你看你看,你瞧你瞧,这才 \(18\) 行代码。但要是你写线段树啊,那不是怎么压行都至少有个 \(50\) 多行代码吗?树状数组多方便啊!所以说,线段树不是个好东西,千万别用

线段树

线段树,码量大,常数大,还难写难调,不是什么蛮好的算法。但是用处广呐!区间修改区间查询全能一套整上!时间复杂度 \(O(n \log n)\),也还好。

来,上例题!

显然的区修区查。

线段树的精髓在于 LazyTag,即懒标记。

哎由于这不是讲题的学习笔记所以不细说了。

上代码!(很丑)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5+5;
int n,m;LL a[N],s[N*4],t[N*4];
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void write(LL x){if(x>9)write(x/10);putchar(x%10+'0');}
int ls(int x){return (x<<1);}int rs(int x){return (x<<1|1);}
void push_up(int x){s[x]=s[ls(x)]+s[rs(x)];return;}
void build(int x,int l,int r){
    if(l==r){s[x]=a[l];return;}
    int mid=(l+r)>>1;
    build(ls(x),l,mid);build(rs(x),mid+1,r);
    push_up(x);t[x]=0;return;
}
void add(int x,int l,int r,LL k){s[x]+=(r-l+1)*k;t[x]+=k;return;}
void push_down(int x,int l,int r){
    if(!t[x])return;int mid=(l+r)>>1;
    add(ls(x),l,mid,t[x]);add(rs(x),mid+1,r,t[x]);t[x]=0;return;
}
void change(int x,int l,int r,int L,int R,LL k){
    if(r<L||R<l)return;
    if(L<=l&&r<=R){add(x,l,r,k);return;}
    push_down(x,l,r);int mid=(l+r)>>1;
    change(ls(x),l,mid,L,R,k);
    change(rs(x),mid+1,r,L,R,k);
    push_up(x);return;
}
LL ask(int x,int l,int r,int L,int R){
    if(r<L||R<l)return 0;if(L<=l&&r<=R)return s[x];
    push_down(x,l,r);int mid=(l+r)>>1;
    return ask(ls(x),l,mid,L,R)+ask(rs(x),mid+1,r,L,R);
}
int main(){
    n=read(),m=read();for(int i=1;i<=n;i++)a[i]=read();build(1,1,n);
    while(m--){
        int opt=read();
        if(opt==1){int x=read(),y=read();LL k=read();change(1,1,n,x,y,k);}
        else{int x=read(),y=read();write(ask(1,1,n,x,y));cout<<"\n";}
    }
    return 0;
}

算是压行压得很厉害的了。只有 \(47\) 行!

那么这就是线段树啦。

顺便贴上线段树的另一个模版题链接。这个就是多加了个乘法!加乘混合,LazyTag 就麻烦一些啦。

代码……请原谅,懒人儿就要有懒样儿,所以懒得贴了。(

接下来上习题!等一下我去找题

行哈!找到了

这个题倒是蛮有意思的。调也调了半天

主要就是你的线段树需要维护四个东西,最大的数,最大数的个数,次大的数,次大数的个数。然后合并的时候,也就是我们的 push_up,需要一个比较复杂的东西。有搁那一个个比较的,但是马良巨大,因此我用的是无聊 map 以及无聊 priority_queue我真是个 STL 高手,就写个合并用俩 STL。怎么说呢,整个的细节还是很多的,超难调

上个代码吧。码风奇异,当时调来调去的写得也有点乱,将就看吧。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5+5;int n,Q;LL a[N];
struct node{int num1,num2,cnt1,cnt2;}s[N*4];
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int ls(int x){return (x<<1);}int rs(int x){return (x<<1|1);}
void push_up(int x){
    map<int,int> mp;mp.clear();
    priority_queue<int> q;while(!q.empty())q.pop();
    q.push(s[ls(x)].num1),q.push(s[ls(x)].num2);
    q.push(s[rs(x)].num1),q.push(s[rs(x)].num2);
    mp[s[ls(x)].num1]+=s[ls(x)].cnt1,mp[s[ls(x)].num2]+=s[ls(x)].cnt2;
    mp[s[rs(x)].num1]+=s[rs(x)].cnt1,mp[s[rs(x)].num2]+=s[rs(x)].cnt2;
    if(mp.size()==1){
        s[x].num1=q.top(),s[x].num2=q.top();
        s[x].cnt1=mp[s[x].num1],s[x].cnt2=s[x].cnt1;return;
    }
    s[x].num1=q.top(),s[x].cnt1=mp[q.top()];while(q.top()==s[x].num1)q.pop();
    s[x].num2=q.top(),s[x].cnt2=mp[q.top()];q.pop();return;

}
void build(int x,int l,int r){
    if(l==r){s[x]={a[l],0,1,0};return;}
    int mid=(l+r)>>1;build(ls(x),l,mid);
    build(rs(x),mid+1,r);push_up(x);
    return;
}
void change(int x,int l,int r,int LR,LL k){
    if(l==LR&&r==LR){s[x]={k,0,1,0};return;}int mid=(l+r)>>1;
    if(LR<=mid)change(ls(x),l,mid,LR,k);
    else change(rs(x),mid+1,r,LR,k);push_up(x);return;
}
node SeCon(node x,node y){
    map<int,int> mp;mp.clear();node A;
    priority_queue<int> q;while(!q.empty())q.pop();
    q.push(x.num1),q.push(x.num2);
    q.push(y.num1),q.push(y.num2);
    mp[x.num1]+=x.cnt1,mp[x.num2]+=x.cnt2;
    mp[y.num1]+=y.cnt1,mp[y.num2]+=y.cnt2;
    if(mp.size()==1){
        A.num1=q.top(),A.num2=q.top();
        A.cnt1=mp[A.num1],A.cnt2=A.cnt1;return A;
    }
    A.num1=q.top(),A.cnt1=mp[q.top()];
    while(q.top()==A.num1)q.pop();
    A.num2=q.top(),A.cnt2=mp[q.top()];q.pop();return A;
}
node ask(int x,int l,int r,int L,int R){
    if(L<=l&&r<=R)return s[x];int mid=(l+r)>>1;
    if(R<=mid)return ask(ls(x),l,mid,L,R);
    if(L>mid)return ask(rs(x),mid+1,r,L,R);
    return SeCon(ask(ls(x),l,mid,L,R),ask(rs(x),mid+1,r,L,R));
}
int main(){
    n=read(),Q=read();for(int i=1;i<=n;i++)a[i]=read();build(1,1,n);
    while(Q--){
        int opt=read();
        if(opt==1){int x=read();LL k=read();change(1,1,n,x,k);}
        else{
            int x=read(),y=read();
            node A=ask(1,1,n,x,y);
            cout<<(A.num2==A.num1?0:A.cnt2)<<"\n";
        }
    }
    return 0;
}

这也应该有个七八十行代码吧,细节又堆积成山。因此尽量还是别用线段树,难写难调。唉,但谁要它用处大呢!所以还是得学呐!

总结

树状数组是个好东西,线段树不是个好东西,嗯然后没然后了

这总结得挺对,划掉干啥(

所以说,树状数组的话其实上随你用的,没细节,码量又小得可怜,多亲切啊。线段树的话,如果考场上遇到,一定不要上来就写,最后留个一个小时左右攻克就行。一遍过了倒算,要是出了什么小小的 bug,把自己心态调崩了,那做后面的题可就没心情了,而且还浪费了很多时间。

线段树啊!要完全掌握你,第一步是学会调试呐!

posted @ 2025-07-19 15:23  嘎嘎喵  阅读(25)  评论(0)    收藏  举报