学完了线段树的基础知识(建树,修改,查询等操作)后,来看看下面这题
注:原题链接:https://www.luogu.org/problemnew/show/P3372
读完这题,是不是有跃跃欲试的感觉?
1)查询区间和 简单
2)区间修改???
for(int i=l;i<=r;i++)add(1,i,val);
所以,智障的我就写出了这样的代码
然后,稳稳的TLE
我们来分析一下上述代码的时间复杂度
1. 查询操作 O ( nlogn )
2. 区间修改 每一次单点修改O(n),总共n次,O(nlogn)
100000*100000*log(100000) TLE!!!
所以,这时候,我们需要——
懒标记
1.懒标记用途
懒标记的作用是,在区间修改的时候,我们在需要修改的区间放个标记,下次要用的时候再打开。
这样可以节省大量的时间,可以把区间修改的时间复杂度降至O(log(n))
2.懒标记的实现
上述做法固然解决了区间修改的问题
但是——
我们要查询的时候怎么办,总不能
cout<<"I can't do it"<<endl;
我们需要——
把懒标记拆了
if(tree[root].l==l&&tree[root].r==r)// 找到区间 return tree[root].val+tree[root].tag*(r-l+1);// 计算并返回答案 if(tree[root].tag)//如果有懒标记 { tree[root*2].tag+=tree[root].tag; tree[root*2+1].tag+=tree[root].tag;//下传懒标记 tree[root].val+=(tree[root].r-tree[root].l+1)*tree[root].tag;//计算答案 tree[root].tag=0;//清空标记 }
这样,我们就完成了查询和清除懒标记的工作。
回过头谈谈修改
void add(int root,int l,int r,int val) { if(tree[root].l==l&&tree[root].r==r) { tree[root].tag+=val; return; } tree[root].val+=(r-l+1)*val; if(tree[root].mid>=r)add(root*2,l,r,val); else if(tree[root].mid<l)add(root*2+1,l,r,val); else add(root*2,l,tree[root].mid,val),add(root*2+1,tree[root].mid+1,r,val); }
找到区间放标记,回头再来查答案
3.线段树经典题目
luogu P1886 滑动窗口
其实,这题是单调队列的题目
但用线段树依旧能过(只要你的线段树常数不大)
代码:
// luogu-judger-enable-o2 #include<bits/stdc++.h> using namespace std; int n,k; struct T{ int l,r,mid,v; }t1[4000005],t2[4000005]; void build(int root,int l,int r) { int mid=(l+r)/2; t1[root].l=l,t1[root].r=r,t1[root].mid=mid; t2[root].l=l,t2[root].r=r,t2[root].mid=mid; if(l==r) { scanf("%d",&t1[root].v); t2[root].v=t1[root].v; return; } build(root*2,l,mid); build(root*2+1,mid+1,r); t1[root].v=max(t1[root*2].v,t1[root*2+1].v); t2[root].v=min(t2[root*2].v,t2[root*2+1].v); } int query(int a,int b,int root,int flag) { int l=t1[root].l,r=t1[root].r; int mid=(l+r)/2; if(a==l&&b==r)return flag==1?t1[root].v:t2[root].v; if(b<=mid)return query(a,b,root*2,flag); else if(a>mid)return query(a,b,root*2+1,flag); else return flag==1?max(query(a,mid,root*2,flag),query(mid+1,b,root*2+1,flag)):min(query(a,mid,root*2,flag),query(mid+1,b,root*2+1,flag)); } int main() { scanf("%d%d",&n,&k); build(1,1,n); for(int f=0;f<=1;f++) { for(int i=1;i<=n-k+1;i++) printf("%d ",query(i,i+k-1,1,f)); printf("\n"); } return 0; }
简单的线段树练手题。
顺便说下单调队列的方法
#include<bits/stdc++.h> #define top front using namespace std; int n,k; int a[1000005]; deque<int>q; int main() { cin>>n>>k; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=k;i++) { while(!q.empty()&&a[q.back()]>a[i])q.pop_back(); q.push_back(i); } cout<<a[q.front()]<<' '; for(int i=k+1;i<=n;i++) { while(!q.empty()&&a[q.back()]>a[i])q.pop_back(); q.push_back(i); while(!q.empty()&&q.front()<=i-k)q.pop_front(); cout<<a[q.front()]<<' '; } cout<<endl; while(!q.empty())q.pop_front(); for(int i=1;i<=k;i++) { while(!q.empty()&&a[q.back()]<a[i])q.pop_back(); q.push_back(i); } cout<<a[q.front()]<<' '; for(int i=k+1;i<=n;i++) { while(!q.empty()&&a[q.back()]<a[i])q.pop_back(); q.push_back(i); while(!q.empty()&&q.front()<=i-k)q.pop_front(); cout<<a[q.front()]<<' '; } cout<<endl; return 0; }
好了,学完了懒标记,遇到题目直接用线段树吧!