最近学线段树,先来总结一下知道的基础知识。感觉这东西很灵活。
自己现在的理解就是线段树是来维护一个序列的区间查找与修改操作的数据结构,我觉得学好它可能更容易理解树状数组。就像有的网站上说,为什么需要线段树,就是因为它综合起来查找和修改的操作复杂度比较低,都是log级别的。基本线段树都包括Build(),Update(),Query(),PushUp()函数。A[]数组存维护的原序列,sum[]数组存区间和。sum[]数组的下标其实是这棵树从根节点到叶子节点的编号,每个编号对应一个区间和,自然sum[1]对应所维护的整个区间。由于sum[]数组不仅存每个点的值,还存区间的值,所以数组要开相当于A[]数组的4倍。
单点更新类:
HDU 1166 线段树功能:update:单点增减 query:区间求和
这是到入门题。说的也很直白,就是区间的查询和修改。用宏定义lson,rson方便一些。
1 #include<cstdio> 2 #define lson l,m,rt<<1 3 #define rson m+1,r,rt<<1|1 4 using namespace std; 5 const int maxn=50005; 6 int A[maxn],sum[maxn<<2]; 7 8 void PushUp(int rt) 9 { 10 sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 11 } 12 13 void Build(int l,int r,int rt)//递归建树 14 { 15 if(l==r){ 16 sum[rt]=A[l];//递归到叶子节点时,把初始值给对应的sum[]数组的值。 17 return; 18 } 19 int m=(l+r)>>1; 20 Build(lson); 21 Build(rson); 22 PushUp(rt);//向上更新父节点的值,刚开始建树时只有存叶子结点值的sum[]有值。 23 } 24 25 void Update(int pos,int val,int l,int r,int rt)//递归更新 26 { 27 if(l==r){ 28 sum[rt]+=val; 29 return; 30 } 31 int m=(l+r)>>1; 32 if(pos<=m) Update(pos,val,lson); 33 else Update(pos,val,rson); 34 PushUp(rt);//也是要向上返回更新父节点 35 } 36 37 int Query(int L,int R,int l,int r,int rt) 38 { 39 if(L<=l&&r<=R){//属于[L,R]直接返回 40 return sum[rt]; 41 } 42 int m=(l+r)>>1; 43 int res=0; 44 if(L<=m) res+=Query(L,R,lson);//有交叉就递归 45 if(R>m) res+=Query(L,R,rson); 46 return res; 47 } 48 49 int main() 50 { 51 int T; 52 scanf("%d",&T); 53 for (int cas=1;cas<=T;cas++) 54 { 55 int N; 56 scanf("%d",&N); 57 for (int i=1;i<=N;i++) scanf("%d",&A[i]); 58 Build(1,N,1); 59 printf("Case %d: ",cas); 60 char op[10]; 61 while(scanf("%s",op)) 62 { 63 int a,b; 64 if(op[0]=='E') break; 65 scanf("%d%d",&a,&b); 66 if(op[0]=='Q') 67 printf("%d ",Query(a,b,1,N,1)); 68 else if(op[0]=='A') 69 Update(a,b,1,N,1); 70 else 71 Update(a,-b,1,N,1); 72 } 73 } 74 return 0; 75 }
HDU 1754 线段树功能:update:单点替换 query:区间最值
这道题就是在把sum[]数组的存区间和作用变为存区间的最大值。所以就把sum[]变成MAX[]了。其实道理是一样的。
1 #include<cstdio> 2 #include<algorithm> 3 #define lson l,m,rt<<1 4 #define rson m+1,r,rt<<1|1 5 using namespace std; 6 const int maxn=200005; 7 int MAX[maxn<<2],A[maxn]; 8 int N,M; 9 10 void PushUp(int rt) 11 { 12 MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]); 13 } 14 15 void Build(int l,int r,int rt) 16 { 17 if(l==r){ 18 MAX[rt]=A[l]; 19 return; 20 } 21 int m=(l+r)>>1; 22 Build(lson); 23 Build(rson); 24 PushUp(rt); 25 } 26 27 void Update(int pos,int val,int l,int r,int rt) 28 { 29 if(l==r){ 30 MAX[rt]=val; 31 return; 32 } 33 int m=(l+r)>>1; 34 if(pos<=m) Update(pos,val,lson); 35 else Update(pos,val,rson); 36 PushUp(rt); 37 } 38 39 int Query(int L,int R,int l,int r,int rt) 40 { 41 if(L<=l&&r<=R){ 42 return MAX[rt]; 43 } 44 int m=(l+r)>>1; 45 int res=0; 46 if(L<=m) res=max(res,Query(L,R,lson)); 47 if(R>m) res=max(res,Query(L,R,rson)); 48 return res; 49 } 50 51 int main() 52 { 53 while(scanf("%d%d",&N,&M)==2) 54 { 55 for (int i=1;i<=N;i++) scanf("%d",&A[i]); 56 Build(1,N,1); 57 int a,b; 58 char op; 59 for (int i=0;i<M;i++){ 60 scanf(" %c",&op); 61 scanf("%d%d",&a,&b); 62 if(op=='Q'){ 63 int ans=Query(a,b,1,N,1); 64 printf("%d ",ans); 65 }else 66 { 67 Update(a,b,1,N,1); 68 } 69 } 70 } 71 return 0; 72 }
HDU 1394 线段树功能:update:单点增减 query:区间求和
这道题求最小逆序数。线段树怎么求逆序数呢?就是依次读入序列时,把读入的值加入线段树,每次查询时,就看比这个值大的区间有多少个元素,就是这个数对应逆序数的个数了。而这道题不仅让求逆序数,而且序列还可以一位一位向后移,要求最少的逆序数。既然刚开始读入序列已经知道了一个逆序数个数,那么后面考虑的就是向后移动一个数字会对当前结果造成什么影响。比如说序列是:1 3 6 9 0 8 5 7 4 2 当我把1放在最后面时,只需考虑有关1的逆序数增减。可以发现,对于题目要求的n个数的序列,每次往后移动一个数a[i],也就是a[i]放在了最后(这里理解好就懂了,我想了挺长时间),增加的逆序数个数为n-a[i]-1(就是比a[i]大的数的个数),减少的逆序数个数为a[i](自然就是不大于a[i]的数)。所以每移动一次,逆序数就变为cnt+=n-a[i]-1-a[i].
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #define lson l,m,rt<<1 5 #define rson m+1,r,rt<<1|1 6 using namespace std; 7 const int maxn=5005; 8 int a[maxn],sum[maxn<<2]; 9 int n; 10 11 void PushUp(int rt) 12 { 13 sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 14 } 15 16 void Build(int l,int r,int rt) 17 { 18 sum[rt]=0; 19 if(l==r) return; 20 int m=(l+r)>>1; 21 Build(lson); 22 Build(rson); 23 } 24 25 void Update(int pos,int l,int r,int rt) 26 { 27 if(l==r){ 28 sum[rt]++; 29 return; 30 } 31 int m=(l+r)>>1; 32 if(pos<=m) Update(pos,lson); 33 else Update(pos,rson); 34 PushUp(rt); 35 } 36 37 int Query(int L,int R,int l,int r,int rt) 38 { 39 if(L<=l&&r<=R){ 40 return sum[rt]; 41 } 42 int res=0; 43 int m=(l+r)>>1; 44 if(L<=m) res+=Query(L,R,lson); 45 if(R>m) res+=Query(L,R,rson); 46 return res; 47 } 48 49 int main() 50 { 51 while(scanf("%d",&n)==1) 52 { 53 Build(0,n-1,1); 54 int cnt=0; 55 for (int i=0;i<n;i++) 56 { 57 cin>>a[i]; 58 cnt+=Query(a[i],n-1,0,n-1,1); 59 Update(a[i],0,n-1,1); 60 } 61 int ans=cnt; 62 for (int i=0;i<n;i++){ 63 cnt+=n-a[i]-1-a[i]; 64 ans=min(ans,cnt); 65 } 66 cout<<ans<<endl; 67 } 68 return 0; 69 }
HDU 2795 线段树功能:query:区间求最大值的位置(直接把update的操作在query里做了)
这道题把h当作原来A[],相当于每一层,开始赋值都是w。所以这里也用MAX[]数组替代sum[]数组维护区间的最大值,因为层数小,能够贴的位置先贴。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #define lson l,m,rt<<1 5 #define rson m+1,r,rt<<1|1 6 using namespace std; 7 const int maxn=200005; 8 int MAX[maxn<<2]; 9 int n,h,w; 10 11 void PushUp(int rt) 12 { 13 MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]); 14 } 15 16 void Build (int l,int r,int rt) 17 { 18 MAX[rt]=w; 19 if(l==r) return; 20 int m=(l+r)>>1; 21 Build(lson); 22 Build(rson); 23 } 24 25 int Query(int val,int l,int r,int rt) 26 { 27 if(l==r){ 28 MAX[rt]-=val; 29 return l; 30 } 31 int m=(l+r)>>1; 32 int res=(MAX[rt<<1]>=val)?Query(val,lson):Query(val,rson); 33 PushUp(rt); 34 return res; 35 } 36 37 int main() 38 { 39 while(scanf("%d%d%d",&h,&w,&n)==3) 40 { 41 if(h>n) h=n; 42 Build(1,h,1); 43 int x; 44 for (int i=0;i<n;i++) 45 { 46 scanf("%d",&x); 47 if(MAX[1]<x) 48 printf("-1 "); 49 else 50 printf("%d ",Query(x,1,h,1)); 51 } 52 } 53 return 0; 54 }
参考资料:
【2】http://blog.csdn.net/zearot/article/details/52280189