牛客算法竞赛入门笔记5

本文介绍了线段树的基本概念,作为区间数据结构,线段树能实现O(logn)的区间访问和修改操作。文章通过若干例题详细讲解了线段树的应用,包括区间最大值与最小值差、区间加法、区间乘法等操作,并对比了线段树与树状数组的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

终于来了,线段树与树状数组,咱们先浅浅地发一点,更多问题敬请期待(。

再浅浅地描述一下线段树:

啥是线段树呢?其实就是一种区间数据结构,它是一颗树的样子,但是每个节点维护的是某一个区间的属性,根节点维护的整个区间,每个节点有两个孩子,分别维护父亲区间的左半段和右半段,这样我们就可以通过区间的拼接完成O(logn)地访问区间的属性,而区间的修改操作也同样可以O(logn)地完成,只不过在每次区间修改时,如果我发现当前这个区间已经是我要修改的大区间的一个子区间了,那么我们就没有必要再往下走去修改每个单点,直接修改当前这个父亲区间即可,但是因为此时孩子没有被修改,我们需要做一个标记,保证下次我无可避免地需要访问这段区间的某个子区间的值的时候能够正确地计算出它现在的值,这个标记就被叫做lazy标记。

lazy标记的判断:

1-子区间是否能合并成大区间

2-对区间整体的修改能不能直接反馈到区间的值上而不经过子区间的计算

Balanced Lineup (nowcoder.com)

这,板子题四星,我不理解。

题意:给一串数,询问区间最大值和最小值的差

思路:没有思路,线段树直接写,每次维护区间最大值和最小值即可

 #include <bits/stdc++.h>
 using namespace std;
 #define INF 0x3f3f3f3f
 const int maxn=100001;
 int val[maxn];
 int tr_ma[maxn<<2];
 int tr_mn[maxn<<2];
 void build(int p,int l,int r){
     if(l==r){
         tr_ma[p]=tr_mn[p]=val[l];
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tr_ma[p]=max(tr_ma[ls],tr_ma[rs]);
     tr_mn[p]=min(tr_mn[ls],tr_mn[rs]);
     return;
 }
 ​
 int ma(int p,int l,int r,int a,int b){
     if(a<=l&&r<=b){
         return tr_ma[p];
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     int ans1=0,ans2=0;
     if(a<=mid){
         ans1=ma(ls,l,mid,a,b);
     }
     if(b>mid){
         ans2=ma(rs,mid+1,r,a,b);
     }
     return max(ans1,ans2);
 }
 ​
 int mn(int p,int l,int r,int a,int b){
     if(a<=l&&r<=b){
         return tr_mn[p];
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     int ans1=INF,ans2=INF;
     if(a<=mid){
         ans1=mn(ls,l,mid,a,b);
     }
     if(b>mid){
         ans2=mn(rs,mid+1,r,a,b);
     }
     return min(ans1,ans2);
 }
 int main(){
     int n,q;
     cin>>n>>q;
     for(int i=1;i<=n;i++){
         cin>>val[i];
     }
     build(1,1,n);
     for(int i=0;i<q;i++){
         int a,b;
         cin>>a>>b;
         cout<<ma(1,1,n,a,b)-mn(1,1,n,a,b)<<endl;
     }
     return 0;
 }

A Simple Problem with Integers (nowcoder.com)

板子题

题意:区间加,问区间和

思路:带lazy标记的线段树板子,最重要的是标记下传和根据标记修改区间值的操作,只要理清楚怎么把父亲的标记传给儿子并同时修改儿子的值即可

 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 const int maxn=100001;
 int x[maxn];
 int tree[maxn<<2],lazy[maxn<<2];
 void build(int p,int l,int r){
     if(l==r){
         tree[p]=x[l];
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tree[p]=tree[ls]+tree[rs];
 }
 ​
 ​
 void push_down(int p,int l ,int r){
     if(l==r){
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     lazy[ls]+=lazy[p];
     lazy[rs]+=lazy[p];
     tree[ls]+=lazy[p]*(mid-l+1);
     tree[rs]+=lazy[p]*(r-mid);
     lazy[p]=0;
 }
 ​
 int calc(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tree[p];
     }
     if(lazy[p]!=0){
         push_down(p,l,r);
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int ans1=0,ans2=0;
     if(x<=mid){
         ans1=calc(ls,l,mid,x,y);
     }
     if(y>mid){
         ans2=calc(rs,mid+1,r,x,y);
     }
     return ans1+ans2;
 }
 ​
 void add(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         lazy[p]+=val;
         tree[p]+=(r-l+1)*val;
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(lazy[p]!=0){
         push_down(p,l,r);
     }
     if(x<=mid){
         add(ls,l,mid,x,y,val);
     }
     if(y>mid){
         add(rs,mid+1,r,x,y,val);
     }
     tree[p]=tree[ls]+tree[rs];
     return;
 }
 ​
 signed main(){
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>x[i];
     }
     build(1,1,n);
     for(int i=0;i<m;i++){
         char flag;
         cin>>flag;
         if(flag=='Q'){
             int a,b;
             cin>>a>>b;
             cout<<calc(1,1,n,a,b)<<endl;
         }else{
             int a,b,c;
             cin>>a>>b>>c;
             add(1,1,n,a,b,c);
         }
     }
     return 0;
 }

数据结构 (nowcoder.com)

这个题我倒是觉得挺难的,但是只有两星。。。

题意:四种操作:区间加,区间乘,询问区间和,询问区间元素的平方和

思路:首先这题有两种操作,如果分成两道题目,那就很简单了,但是放在一起就会出现一个问题:当这个区间同时拥有乘的标记和加的标记时,我应该先进行哪个操作?其实都可以,我们只要人为规定一个形式,保证下面所有的维护都能遵守这个规定即可,比如我们规定当需要同时维护加法和乘法时,总是保证先乘再加,即永远满足kx+b的形式,那么当我有新的标记下传过来时,我的两个标记就会变为(k1x+b1)*k2+b2=k1k2x+b1k2+b2,即此时的乘标记改为k1k2,加标记改为b1k2+b2,这样标记下传就完成了,下面就是根据这个新的乘和加的标记去修改区间的值,怎么做呢?很简单,我们列一个式子推导一下即可(len为区间长度)

可以看到,我们只需要知道原区间和和原区间平方和,便可以根据两个标记计算出新的答案,带入计算即可

顺便bb一句:为什么题面不是qc姐姐(

 //代码很长,但其实会写那个push_down基本就只剩板子了
 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 const int maxn=10005;
 int tr_sum[maxn<<2];
 int tr_plus[maxn<<2];
 int lazy_add[maxn<<2];
 int lazy_plus[maxn<<2];
 int x[maxn];
 void build(int p,int l,int r){
     if(l==r){
         tr_sum[p]=x[l];
         tr_plus[p]=x[l]*x[l];
         lazy_plus[p]=1;
         lazy_add[p]=0;
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs]; 
     lazy_plus[p]=1;
     lazy_add[p]=0;
     return;
 }
 ​
 void push_down(int p,int l,int r){
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     
     int k=lazy_plus[p];
     int val=lazy_add[p];
     
     tr_plus[ls]=k*k*tr_plus[ls]+2*k*val*tr_sum[ls]+(mid-l+1)*val*val;   
     tr_plus[rs]=k*k*tr_plus[rs]+2*k*val*tr_sum[rs]+(r-mid)*val*val;
 ​
     tr_sum[ls]*=k;
     tr_sum[rs]*=k;
     tr_sum[ls]+=(mid-l+1)*val;
     tr_sum[rs]+=(r-mid)*val;
     
     lazy_plus[ls]*=k;
     lazy_plus[rs]*=k;
     
     lazy_add[ls]*=k;
     lazy_add[rs]*=k;
     lazy_add[ls]+=val;
     lazy_add[rs]+=val;
     
     lazy_add[p]=0;
     lazy_plus[p]=1;
     return;
 } 
 ​
 int calc_sum(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tr_sum[p];
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int sum1=0,sum2=0;
     if(x<=mid){
         sum1=calc_sum(ls,l,mid,x,y);
     }
     if(y>mid){
         sum2=calc_sum(rs,mid+1,r,x,y);
     }
     return sum1+sum2;
 }
 ​
 int calc_plus(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tr_plus[p];
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int sum1=0,sum2=0;
     if(x<=mid){
         sum1=calc_plus(ls,l,mid,x,y);
     }
     if(y>mid){
         sum2=calc_plus(rs,mid+1,r,x,y);
     }
     return sum1+sum2;
 } 
 ​
 void pls(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         
         lazy_plus[p]*=val;
         lazy_add[p]*=val;
         tr_sum[p]*=val;
         tr_plus[p]*=val*val;
         return;
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(x<=mid){
         pls(ls,l,mid,x,y,val);
     }
     if(y>mid){
         pls(rs,mid+1,r,x,y,val);
     }
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs];
 }
 ​
 void add(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         lazy_add[p]+=val;
         tr_plus[p]+=tr_sum[p]*2*val+val*val*(r-l+1);
         tr_sum[p]+=(r-l+1)*val;
         return;
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(x<=mid){
         add(ls,l,mid,x,y,val);
     }
     if(y>mid){
         add(rs,mid+1,r,x,y,val);
     }
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs];
 }
 ​
 ​
 signed main(){
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>x[i];
     }
     build(1,1,n);
     for(int i=0;i<m;i++){
         int flag;
         cin>>flag;
         if(flag==1){
             int a,b;
             cin>>a>>b;
             cout<<calc_sum(1,1,n,a,b)<<endl;
         }else if(flag==2){
             int a,b;
             cin>>a>>b;
             cout<<calc_plus(1,1,n,a,b)<<endl;
         }else if(flag==3){
             int a,b,c;
             cin>>a>>b>>c;
             pls(1,1,n,a,b,c);
         }else{
             int a,b,c;
             cin>>a>>b>>c;
             add(1,1,n,a,b,c);
         }
     }
     return 0;
 }

​

逆序数 (nowcoder.com)

来道简单的放松一下,逆序对的个数,三种方法:

1-暴力

2-线段树/树状数组,维护每个数前任意一个区间内数的数量

3-归并排序统计交换次数

毕竟线段树专题,所以我写的线段树

 #include <bits/stdc++.h>
 using namespace std;
 const int maxn=100001;
 #define int long long//没开long long wa1,呜呜呜
 int tree[maxn<<2];
 void add(int p,int l,int r,int pos){
     if(l==r){
         tree[p]++;
         return;
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     if(pos<=mid){
         add(ls,l,mid,pos);
     }else{
         add(rs,mid+1,r,pos);
     }
     tree[p]=tree[ls]+tree[rs];
 }
 int query(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tree[p];
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     if(y<=mid){
         return query(ls,l,mid,x,y);
     }else if(x>mid){
         return query(rs,mid+1,r,x,y);
     }else{
         return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y);
     }
 }
 signed main(){
     int n;
     cin>>n;
     int ans=0;
     for(int i=0;i<n;i++){
         int a;
         cin>>a;
         ans+=query(1,1,maxn,a+1,maxn);
         add(1,1,maxn,a);
     }
     cout<<ans<<endl;
     return 0;
 }

Atlantis (nowcoder.com)

经典矩阵覆盖问题

题意:给你N个矩阵,计算出他们覆盖的总面积

思路:线段树+扫描线+离散化,写了一天,只能说很无语,因为自己太**了,首先,我们要有一个扫描线的思想,这题直接就把所有的矩形都给你,让你静态地去计算即可,所以我们从上往下一行一行地看,可以观察到,只要我们能够实时维护出对每一个Y来说,当前x方向上被矩形覆盖住的区间长度即可,但是,如果我们就直接在x轴上建线段树的话复杂度太高了,而且注意到题目中n的范围只有100,所以我们可以考虑离散化,但是我一开始想的太麻烦了,我本来想用两个map存互相的映射关系,看了题解发现人家直接在tree的节点里记录下两边的端点就能维护出来了。剩下要注意的就是递归的判断条件和左右边界了

 #include <bits/stdc++.h>
 using namespace std;
 const int maxn=210;
 vector<double>xs;
 struct line{
     double x1,x2,y,flag;
 };
 line lines[201];
 bool com(line a,line b){
     return a.y>b.y;
 }
 ​
 struct node{
     //left,right:每个区间对应的实际的左右端点 
     //lazy:这个区被多少条线段覆盖
     //len:这个区间内未被覆盖的子区间的长度 
     double left,right,len;
     int lazy;
 };
 node tree[maxn<<2];
 ​
 void build(int p,int l,int r){
     tree[p].left=xs[l];
     tree[p].right=xs[r];
     tree[p].len=tree[p].lazy=0;
     if(r-l==1){
         return;
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     build(ls,l,mid);
     build(rs,mid,r);
 }
 ​
 void change(int p,int l,int r){
     if(tree[p].lazy>0){
         tree[p].len=tree[p].right-tree[p].left;
     }else if(r-l==1){
         tree[p].len=0;
     }else{
         tree[p].len=tree[p<<1].len+tree[p<<1|1].len;
     }
 }
 ​
 void update(int p,int l,int r,double x1,double x2,int flag){
     if(tree[p].left==x1&&tree[p].right==x2){
         if(flag==0){
             tree[p].lazy++;
         }else{
             tree[p].lazy--;
         }
     }else{
         int ls=p<<1;
         int rs=p<<1|1;
         int mid=(l+r)>>1;
         if(tree[ls].right>=x2){
             update(ls,l,mid,x1,x2,flag);
         }else if(tree[rs].left<=x1){
             update(rs,mid,r,x1,x2,flag);
         }else{
             update(ls,l,mid,x1,tree[ls].right,flag);
             update(rs,mid,r,tree[rs].left,x2,flag);
         }
     }
     change(p,l,r);
     return;
 } 
 ​
 int main(){
     
     int n,ca=1;
     while(cin>>n&&n!=0){
         xs.clear();
         xs.push_back(-1);
         for(int i=1;i<=2*n;i+=2){
             cin>>lines[i].x1>>lines[i].y>>lines[i].x2;
             lines[i].flag=1;
             lines[i+1].x1=lines[i].x1;
             lines[i+1].x2=lines[i].x2;
             cin>>lines[i+1].y;
             lines[i+1].flag=0;
             xs.push_back(lines[i].x1);
             xs.push_back(lines[i].x2);
         }
         sort(xs.begin(),xs.end());
         sort(lines+1,lines+1+2*n,com);
         build(1,1,2*n);
         double h=lines[1].y;
         double ans=0;
         update(1,1,2*n,lines[1].x1,lines[1].x2,lines[1].flag);  
         for(int i=2;i<=n*2;i++){
             double mar=h-lines[i].y;
             h=lines[i].y;
             ans+=mar*tree[1].len;
             update(1,1,2*n,lines[i].x1,lines[i].x2,lines[i].flag);  
         }       
         cout<<"Test case #"<<ca++<<endl;
         cout<<"Total explored area: "<<ans<<endl;
     }
     
     return 0;
 }

树状数组:

比线段树常数小,实现简单,在单点修改+区间查询,或者区间修改+单点查询的时候比较好用

数星星 Stars (nowcoder.com)

这个题我大无语,我还没看树状数组的时候拿线段树写,结果超时了,网上搜题解发现全用的树状数组,还有人说这个题卡了线段树的做法,我人傻了,就放到后面学了树状数组之后做,结果它又t了,后来才发现原来是因为x和y有可能等于0,所以要往后存一位,然后我又去改了那个线段树的做法,加了快读只要9ms,咱就是说咱很无语

题意:给定n个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。

思路:由于这个题有两个变量x和y,所以我们想办法把其中某个变量的影响消除,即放到时间轴上,而本题也给了提示:星星按照y坐标增序给出,y相同的按照x坐标增序给出,这样一来,对于每一个星星,有可能成为它的左下方的星星的只有前面已经读入的、横坐标小于等于它的星星,因此我们用线段树或树状数组进行单点修改,然后区间查询即可。

 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 #define MAXN 32800
 ​
 void read(int &x){
     int f=1;x=0;char s=getchar();
     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
     x*=f;
 }
 ​
 int tree[MAXN];
 inline int lowbit(int x){
     return x&(-x);
 } 
 inline void add(int x,int y,int n){
     for(int i=x;i<=n;i+=lowbit(i)){
         tree[i]+=y;
     }
 }
 ​
 inline int sum(int x){
     int ans=0;
     while(x>0){
         ans+=tree[x];
         x-=lowbit(x);       
     }
     return ans;
 }
 int ans[15001];
 signed main(){
     int n;
     read(n);
     int num=32768;
     for(int i=0;i<n;i++){
         int x,y;
         read(x);
         read(y); 
         ans[sum(x+1)]++;
         add(x+1,1,num); 
     }
     for(int i=0;i<n;i++){
         printf("%lld\n",ans[i]); 
     }
     return 0;
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuhudaduizhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值