线段树学习总结
这个星期主要是进行线段树的复习与进一步学习,做了些题目,将我的错误与注意事项总结在此。
在这次的学习中,我才算是基本会使用了线段树,不再像以前,只能当做一种帮忙整段修改区间,求解区间最大值的数据结构,线段树的优越就在于它的树形结构,将任何操作都降到了log级别的,在此基础上可以记录很多数据,而并非只有节点的值。
我的线段树习惯如下:
此种写法参考了http://www.notonlysuccess.com/index.php/segment-tree-complete
1, 用root表示当前节点,不记录节点区间,而是在递归过程中随着函数的改变自动更新。
2, PushUp() 是把当前结点的信息更新到父结点
3, PushDown()是把当前结点的信息更新给子节点
4, 要更改的区间用全局变量x,y记录下,不在递归的过程中传递
做了些题目,将其中2道说下 :
POJ 3468
成段增减,区间求和问题
要注意的数据范围,要使用longlong来存储,另外需要注意的是用scanf()读入long long的时候还是要用“%I64d”,因为会有负数的出现,在正整数的int范围内使用什么都无所谓。
下边是部分的代码
1 //更新结点信息,即加减处理 2 void updata(int l,int r,int root) 3 { 4 if ((y<l)||(x>r)) return; 5 6 if ((x<=l)&&(y>=r)) 7 { 8 a[root][0]+=c*(r-l+1); 9 a[root][1]+=c; 10 return; 11 } 12 13 int mid=(l+r)/2; 14 //必须是临时变量,避免递归时改变 15 16 //left child 17 a[root*2][0]+=a[root][1]*(mid-l+1); 18 a[root*2][1]+=a[root][1]; 19 //right child 20 a[root*2+1][0]+=a[root][1]*(r-mid); 21 a[root*2+1][1]+=a[root][1]; 22 //itself 23 a[root][1]=0; 24 25 updata(l,mid,root*2); 26 updata(mid+1,r,root*2+1); 27 //change Max 28 a[root][0]=a[root*2][0]+a[root*2+1][0]; 29 } 30 31 //寻找区间累加和 32 int find(int l,int r,int root) 33 { 34 if ((y<l)||(x>r)) return 0; 35 if ((x<=l)&&(y>=r)) return a[root][0]; 36 37 int mid=(l+r)/2; 38 //left child 39 a[root*2][0]+=a[root][1]*(mid-l+1); 40 a[root*2][1]+=a[root][1]; 41 //right child 42 a[root*2+1][0]+=a[root][1]*(r-mid); 43 a[root*2+1][1]+=a[root][1]; 44 //itself 45 a[root][1]=0; 46 47 int m1=find(l,mid,root*2); 48 int m2=find(mid+1,r,root*2+1); 49 50 return m1+m2; 51 }
POJ 3667
要求解的是寻找长度为len的最靠左边的一段区间,将其占据,或者将一段长度的区间释放。
这次线段树要记录的就不是结点的值了,而是区间内的一些特殊的值,如下所示
1 struct tree 2 { 3 int l; //从左端点开始的连续最大长度 4 int r; //以右端点结束的连续最大长度 5 int sum; //区间内最大连续长度 6 int cover; //区间状态(是否覆盖 : 0,1,-1) 7 } a[maxn*4];
cover域的数字,0表示全为空的,1表示全被占了,-1为其他情况,具体的思路见代码注释
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn=80100; struct tree { int l; //从左端点开始的连续最大长度 int r; //以右端点结束的连续最大长度 int sum; //区间内最大连续长度 int cover; //区间状态(是否覆盖 : 0,1,-1) } a[maxn*4]; int n,m,color,len,x,y; //初始化,每段空房间数为r-l+1 void Build(int l,int r,int root) { a[root].l=a[root].r=a[root].sum=r-l+1; a[root].cover=0; if (l==r) return; int mid=(l+r)/2; Build(l,mid,root*2); Build(mid+1,r,root*2+1); } void PushDown(int l,int r,int root) { //Updata children's state if (a[root].cover!=-1) { a[root*2].cover=a[root*2+1].cover=a[root].cover; int mid = (l+r)/2; a[root*2].l=a[root*2].r=a[root*2].sum = a[root].cover? 0 : mid-l+1; a[root*2+1].l=a[root*2+1].r=a[root*2+1].sum = a[root].cover? 0 : r-mid; a[root].cover=-1; } } //updata father void PushUp(int l,int r,int root) { a[root].l = a[root*2].l; a[root].r = a[root*2+1].r; int mid = (l+r)/2; if (a[root].l==mid-l+1) a[root].l += a[root*2+1].l; if (a[root].r==r-mid) a[root].r += a[root*2].r; a[root].sum = max(a[root*2].r+a[root*2+1].l,max(a[root*2+1].sum,a[root*2].sum)); } void updata(int l,int r,int root) { if (y<l||x>r) return; if (x<=l&&y>=r) { a[root].sum=a[root].l=a[root].r = color? 0 : r-l+1; a[root].cover=color; return; } PushDown(l,r,root); int mid = (l+r)/2; updata(l,mid,root*2); updata(mid+1,r,root*2+1); PushUp(l,r,root); } int query(int l,int r,int root) { //特殊情况 if (l==r) return l; PushDown(l,r,root); int mid = (l+r)/2; //按照顺序求解答案,要求的是靠左的优先 if (a[root*2].sum>=len) return query(l,mid,root<<1); else if (a[root*2].r+a[root*2+1].l>=len) return mid-a[root*2].r+1; return query(mid+1,r,root*2+1); /* 关于这段我也是理解了半天,如果左节点的最大长度正好的话就进入递归 在递归里面对这段进行计算,满足了l+r,即第二个条件,会输出正确答案也就是当前的l */ } int main() { freopen("hotel.in","r",stdin); freopen("hotel.out","w",stdout); scanf("%d%d",&n,&m); Build(1,n,1); int tmp; for (int i=1; i<=m; i++) { scanf("%d",&tmp); if (tmp==1) { scanf("%d",&len); //find the sum>=len int p; if (a[1].sum<len) p=0; else p = query(1,n,1); printf("%d\n",p); x=p; y=p+len-1; color=1; if (p) updata(1,n,1); } else { scanf("%d%d",&x,&len); y=x+len-1; color=0; updata(1,n,1); } } fclose(stdin); fclose(stdout); return 0; }
学到最后,感想就是线段树就是一个log级别的数据结构,跟区间有关的很多值都可以记录下来然后进行高效的操作,总的来说,就是分成两个操作,修改和查询,具体的区别根据实际的需要我们可以进行具体操作,我学到的可以进行【单点更新,成段更新,区间合并,扫描线】,遇到这类的数据结构,首先要考虑能不能使用线段树求解了。