区间统计:
单点更新:
hdu 1754 I hate it
代码:
#include<stdio.h> #define MAXN 200005 struct Tree { int Max,l,r,m; }ar[4*MAXN]; int n,m,h[MAXN]; int BuildTree(int index,int begin,int end) { int x,y; ar[index].l=begin;ar[index].r=end; ar[index].m=(begin+end)>>1; if(begin<end) { x=BuildTree(2*index,begin,ar[index].m); y=BuildTree(2*index+1,ar[index].m+1,end); ar[index].Max=x>y?x:y; } else ar[index].Max=h[begin]; return ar[index].Max; } int GetMax(int index,int begin,int end) { if(ar[index].l==begin && ar[index].r==end) { return ar[index].Max; } else if(begin>ar[index].m) { return GetMax(index*2+1,begin,end); } else if(end<=ar[index].m) { return GetMax(index*2,begin,end); } int x,y; x=GetMax(index*2,begin,ar[index].m); y=GetMax(index*2+1,ar[index].m+1,end); return x>y?x:y; } void ChangeTree(int index,int x,int y) { if(ar[index].Max<y) { ar[index].Max=y; } if(ar[index].l<ar[index].r) { if(x<=ar[index].m) { ChangeTree(index*2,x,y); } else { ChangeTree(index*2+1,x,y); } } } void init() { int i; for(i=1;i<=n;i++) { scanf("%d",&h[i]); } BuildTree(1,1,n); } void make() { char op[10]; int x,y; while(m--) { scanf("%s%d%d",op,&x,&y); if(op[0]=='Q') { printf("%d\n",GetMax(1,x,y)); } else { ChangeTree(1,x,y); } } } int main() { while(scanf("%d%d",&n,&m)!=EOF) { init(); make(); } return 0; }
相应的练习题:
hdu 1166 敌兵布阵
区间更新:
刚开始的时候,可能有人会想到是否可以用单点更新的思想来做呢?按理来说是可以的,但是有时候往往我们代码的效率就不够好了。这样想,我们可以之间更新区间,但是当我们访问区间下面的节点的时候,不是已经改变了吗?所以我们还得在查询的时候,相应的更新。也叫做延迟更新。这类题适合于中等偏下的题,花不了多少时间。
相应的练习题:
hdu 4107 Gangster
hdu 3397 Sequence operation
区间合并:
这类题最常见的题是:求一个区间连续的一些性质,比如1-n数组里,要么放黑棋,要么放白棋,然后给你一个询问区间[i,j],问你这个区间里黑棋连续最多的为多少。比如最大连续上升(很多时候有伴随着一些改变)等。常见的解法是:在节点里添加连个变量,l,r分别记录,左右连续的个数,然后在依次不断的向上,不断的更新。
这类题比较多,给一个题的代码:
hdu 3308 LCIS
#include<stdio.h> #define MAXN 100005 struct data { int l1,l2,r2,r1; }; struct node { int begin,end,mid; int num; data p; }tree[MAXN<<2]; int n,m,st[MAXN],ans; void SetValue(data &p,int l1,int l2,int r2,int r1) { p.l1=l1; p.l2=l2; p.r2=r2; p.r1=r1; } int Max(int x,int y) { return x>y?x:y; } void update(int index) { int x=0; data p=tree[index<<1].p,q=tree[index<<1 | 1].p; if(st[p.r1]<st[q.l1]) { x=q.l2-p.r2+1; } tree[index].num=Max(x,Max(tree[index<<1].num,tree[index<<1 | 1].num)); if(p.l2+1==q.l1 && st[p.l2]<st[q.l1]) { p.l2=q.l2; } if(p.r1+1==q.r2 && st[p.r1]<st[q.r2]) { q.r2=p.r2; } p.r2=q.r2; p.r1=q.r1; tree[index].p=p; } void BuildTree(int index,int begin,int end) { tree[index].begin=begin;tree[index].end=end; tree[index].mid=(begin+end)>>1; if(begin<end) { int x=0; BuildTree(index<<1,begin,tree[index].mid); BuildTree(index<<1 | 1,tree[index].mid+1,end); update(index); } else { tree[index].num=1; scanf("%d",&st[end]); SetValue(tree[index].p,end,end,end,end); } } void Insert(int index,int x) { if(tree[index].begin==tree[index].end) { return; } if(x<=tree[index].mid) { Insert(index<<1,x); } else { Insert(index<<1 | 1,x); } update(index); } data get(int index,int begin,int end) { data p,q; if(tree[index].begin==begin && tree[index].end==end) { if(ans<tree[index].num) { ans=tree[index].num; } return tree[index].p; } if(end<=tree[index].mid) { return get(index<<1,begin,end); } else if(begin>tree[index].mid) { return get(index<<1 | 1,begin,end); } p=get(index<<1,begin,tree[index].mid); q=get(index<<1 | 1,tree[index].mid+1,end); int x=0; if(st[p.r1]<st[q.l1]) { x=q.l2-p.r2+1; } ans=Max(x,ans); if(p.l2+1==q.l1 && st[p.l2]<st[q.l1]) { p.l2=q.l2; } if(p.r1+1==q.r2 && st[p.r1]<st[q.r2]) { q.r2=p.r2; } p.r2=q.r2; p.r1=q.r1; return p; } void make() { BuildTree(1,0,n-1); int x,y; char op[5]; while(m--) { scanf("%s%d%d",op,&x,&y); if(op[0]=='Q') { ans=0; get(1,x,y); printf("%d\n",ans); } else { st[x]=y; Insert(1,x); } } } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); make(); } return 0; }
相应的练习题:
hdu 3911 Back And White
hdu 4046 Panda
扫描线:
这一阶段主要求的是一些矩形的面积并,周长并,有时候也会有体积并。做这类的题,一般会用到离散化的思想。比如你要插入[4999,6000]这个区间到线段树中,那么你只需要把4999和6000分别映射到两个更小的数x,y然后插入。然后一次扫描线段就可以了。
今天上午写了一个代码:
hdu 1255 覆盖的面积
#include<stdio.h> #include<cmath> #include<algorithm> #define MAXN 2005 using namespace std; double flag[MAXN]; struct data { double y1,y2,x,f; }line[MAXN]; struct node { int begin,end,mid,cover; double x; }st[MAXN<<2]; int n; int cmpdouble(double x,double y) { return x<y; } int cmpd(data p,data q) { return p.x<q.x; } void BuildTree(int t,int begin,int end) { st[t].begin=begin;st[t].end=end; st[t].mid=(begin+end)>>1; st[t].cover=0; st[t].x=0; if(begin>=end-1) return; BuildTree(t<<1,begin,st[t].mid); BuildTree(t<<1 | 1,st[t].mid,end); } void init() { int i,l,r; double x1,x2,y1,y2; for(i=0;i<n;i++) { scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2); l=i<<1;r=l+1; flag[l]=y1; line[l].x=x1; line[l].y1=y1; line[l].y2=y2; line[l].f=1; flag[r]=y2; line[r].x=x2; line[r].y1=y1; line[r].y2=y2; line[r].f=-1; } n=r; sort(flag,flag+n+1,cmpdouble); sort(line,line+n+1,cmpd); BuildTree(1,0,n); } int search(double x) { int l=0,r=n,mid; double y; while(l<=r) { mid=(l+r)>>1; if(fabs(flag[mid]-x)<1e-8) return mid; else if(flag[mid]<x) { l=mid+1; } else { r=mid-1; } } return -1; } double update(int t,int begin,int end,double x,int f) { if(begin>=st[t].end || end<=st[t].begin) return 0; if(st[t].begin+1==st[t].end) { double ans; if(st[t].cover>1) { ans=(x-st[t].x)*(flag[st[t].end]-flag[st[t].begin]); st[t].x=x; st[t].cover+=f; return ans; } else { st[t].cover+=f; st[t].x=x; return 0; } } return update(t<<1,begin,end,x,f)+ update(t<<1 | 1,begin,end,x,f); } void make() { double sum=0; int i,l,r; for(i=0;i<n;i++) { l=search(line[i].y1); r=search(line[i].y2); sum+=update(1,l,r,line[i].x,line[i].f); } printf("%.2lf\n",sum); } int main() { int t; scanf("%d",&t); while(t--) { scanf("%d",&n); init(); make(); } return 0; }
其实还可以优化,留给自己慢慢完成!!!!
相应的练习:
hdu 3642 Get The Treasury(体积合并)
这几天的练习总体说来效果还是可以,至少解决了很多自己不能完成的东西。其实还有其他的线段树解法,它不是常规的。比如:hdu 4007 Dave。
第一次近距离接触线段树,希望自己后面能更好的理解线段树。