作为菜鸟,考完试后又开始 跪题 ,今天刚刚学习了 比较水的线段树;
线段树其实就是一颗二叉树,然后节点包含区间等线段域, 然后根据不同要求来增加不同的域属性; 比如下面的这颗线段树
线段树的操作主要包括 建立 ,插入,删除等操作;
首先是线段树的结构:
struct Tree{ int ma,mi;// 根据需要使用不同的域; int left,right; };
void build(int t,int l,int r) // qihzogn { cow[t].left=l; cow[t].right=r; if(l<=r-1) { int mid=(cow[t].left+cow[t].right)/2; build(t*2,l,mid); build(t*2+1,mid+1,r); cow[t].ma=max(cow[t*2].ma,cow[t*2+1].ma); cow[t].mi=min(cow[t*2].mi,cow[t*2+1].mi); } else { cow[t].ma=cow[t].mi=hei[l]; return ; } }
然后是借用线段树解决了TOJ 上的三道比较简单题: 2762 Balanced Lineup 求一个区间的最值问题;3505. Naughty Mike : 求区间和问题, 2913. Frequent values 稍微复杂一点的线段树(Ps: 一般用线段树的数据量都不小,所以输入输出一般用scanf 和 printf 才不会TLE......)
代码如下:
#include <stdio.h> #include <iostream> #define INF 0x3f3f3f3f using namespace std; const int Max=50002; struct Tree{ int ma,mi; int left,right; }; Tree cow[Max*4]; int hei[Max],ma,mi; void build(int t,int l,int r) // qihzogn { cow[t].left=l; cow[t].right=r; if(l<=r-1) { int mid=(cow[t].left+cow[t].right)/2; build(t*2,l,mid); build(t*2+1,mid+1,r); cow[t].ma=max(cow[t*2].ma,cow[t*2+1].ma); cow[t].mi=min(cow[t*2].mi,cow[t*2+1].mi); } else { cow[t].ma=cow[t].mi=hei[l]; return ; } } void query(int t,int a,int b) { if(a>b) return ; if(cow[t].left==a&&cow[t].right==b) { if(ma<cow[t].ma) ma=cow[t].ma; if(mi>cow[t].mi) mi=cow[t].mi; return ; } else { int mid=(cow[t].left+cow[t].right)/2; if(mid<a) query(t*2+1,a,b); else if(mid>b) query(t*2,a,b); else { query(t*2,a,mid); query(t*2+1,mid+1,b); } } } int main() { int N,Q; while(~scanf("%d%d",&N,&Q)) { for(int i=1;i<=N;i++) scanf("%d",&hei[i]); build(1,1,N); int a,b; for(int i=0;i<Q;i++) { scanf("%d%d",&a,&b); ma=0;mi=INF; query(1,a,b); printf("%d\n",ma-mi); } } }
这个跟上一个大致一样,只不过这个是求区间的和的,涉及到的操作也是 建树 ,查询,更新
代码:
/* Accepted 3505 C++ 2.0K 0'00.16" */ #include <string.h> #include <stdio.h> #define Max_Num 100005 int T,n,s[Max_Num],q,a,b; char ch[10]; struct Tree{ int l,r; long long sum; }; Tree t[Max_Num*3]; void build_tree(int root ,int left,int right) { t[root].l=left; t[root].r=right; if(left<right) { int mid =(left+right)/2; build_tree(root*2,left,mid); build_tree(root*2+1,mid+1,right); t[root].sum=t[root*2].sum+t[root*2+1].sum; } else if(left==right) t[root].sum=s[left]; } long long query(int root,int left,int right) { if(left>right) return 0; if(t[root].l==left&&t[root].r==right) { return t[root].sum; } else { int mid= (t[root].l+t[root].r)/2; if(left>mid) { return query(root*2+1,left,right); } else if(mid>right) { return query(root*2,left,right); } else { return query(root*2,left,mid)+query(root*2+1,mid+1,right); } } } void update(int root,int left,int right) { if(left>right) return ; if(left<right) { int mid= (left+right)/2; if(a>mid) update(root*2+1,mid+1,right); else if(a<=mid) update(root*2,left,mid); t[root].sum=t[root*2].sum+t[root*2+1].sum; } else t[root].sum=s[left]; } int main() { scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1; i<=n;i++) scanf("%d",&s[i]); build_tree(1,1,n); scanf("%d",&q); while(q--) { scanf("%s%d%d",ch,&a,&b); if(strcmp(ch,"Inquire")==0) { printf("%d\n",query(1,a,b)); } else if(strcmp(ch,"Add")==0) { s[a]+=b; update(1,1,n); } else { s[a]-=b; if(s[a]<0) s[a]=0; update(1,1,n); } } } }
/* Accepted 2913 C++ 2.6K 0'00.44" 10616K */ #include <stdio.h> #define Max_Num 100005 #define Max(a,b) (a>b?a:b) struct Tree { int l,r;//节点边界 int ma,val;//区间的最多数量的数目和值 int lma,lval,rma,rval;//左右两端的最多数量的数目和值: 因为需要考虑合并是否产生最大值的问题, //所以需要再加左右两端的域 }; Tree t[Max_Num*3]; int n,q,s[Max_Num],tmp,a,b,ans; void build_tree(int root, int left,int right) {//一般的建树过程 t[root].l=left; t[root].r=right; if(left<right) { int mid=(left+right)/2; build_tree(root*2,left,mid); build_tree(root*2+1,mid+1,right); //以下的if..else 块求出没有考虑中间断开的情况 if(t[root*2].ma>t[2*root+1].ma) { t[root].ma=t[root*2].ma; t[root].val=t[root*2].val; } else { t[root].ma=t[root*2+1].ma; t[root].val=t[root*2+1].val; } t[root].lma =t[root*2].lma; t[root].rma =t[root*2+1].rma; t[root].lval=t[root*2].lval; t[root].rval=t[root*2+1].rval; //当左子树的右端最长的数等于右子树的左端最长的数,表示可以合并 if(t[root*2].rval==t[root*2+1].lval) { tmp=t[root*2].rma+t[root*2+1].lma; if(tmp>t[root].ma) {//是否需要更新当前节点的ma; t[root].ma=tmp; t[root].val=t[root*2].rval; } if(t[root*2].lval==t[root*2+1].lval)//考虑到如果左子树的数都相同的话还要更新下 t[root].lma t[root].lma+=t[root*2+1].lma; if(t[root*2].rval==t[root*2+1].rval)//考虑到如果右子树的数都相同的话还要更新下 t[root].rma t[root].rma+=t[root*2].rma; } } else { t[root].lma=t[root].rma=t[root].ma=1; t[root].lval=t[root].rval=t[root].val=s[left]; } } void query(int root ,int left,int right) { if(left>right) return ; if(t[root].l==left&&t[root].r==right) { if(ans<t[root].ma) ans=t[root].ma; } else { int mid=(t[root].l+t[root].r)/2; if(mid>=right) query(root*2,left,right); else if(mid<left) query(root*2+1,left,right); else { query(root*2,left,mid); query(root*2+1,mid+1,right); //下面是左子树的右端最长的数等于右子树的左端最长的数的情况的合并 int s1=0,s2=0; if(t[root*2].rval==t[root*2+1].lval) { if(s[left]==t[root*2].rval) s1=mid-left+1;//跟建树的一样, 单独考虑子树是不是全相等的情况 else s1=t[root*2].rma; if(s[right]==t[root*2+1].lval) s2=right-mid; else s2=t[root*2+1].lma; if(ans<s1+s2) ans=s1+s2; } } } } int main() { while(scanf("%d",&n)) { if(n==0) break; scanf("%d",&q); for(int i=1; i<=n;i++) scanf("%d",&s[i]); build_tree(1,1,n); while(q--) { scanf("%d%d",&a,&b); ans=0; query(1,a,b); printf("%d\n",ans); } } }