线段树(lazy算法+离散化)
来源:互联网 发布:襄阳华为云计算招聘 编辑:程序博客网 时间:2024/05/16 00:36
最近陪着小六的小学生们补了补线段树
发现我的线段树真是弱鸡到不能在弱了
怪我咯,以前老师赶进度赶得太紧了,我又缺课(我终于明白我为什么那么蒟蒻了)
这里写一写我这几天的学习的一些算(题)法(目)
一、
最简单的
HDOJ 1754
相信很多人开始学习线段树都是从这一题开始的
题目罗罗嗦嗦了一大堆也不就一句话:
给出N个数,两种操作:
1、U x y:修改第x个数的值为y;
2、Q x y:求第x到第y个的最大值,注:x未必比y小
标准的线段树对不对
我们可以理解成总裁管左右两个总经理,总经理管左右两个副总经理,副总经理管左右两个经理……每个管理者都只知道自己的值,不知道他们手下的值,所以他们每次都要问自己的手下……这样就很好理解了
#include<cstring>#include<cstdio>using namespace std;int a[210000];//员工,只负责一开始表达自己的值struct node //管理者(假设它是经理),管理者绝对不是员工,一定要分开来//最底层的管理者只管一个员工,绝对不能想着:反正一个员工,就自己管自己算了。//理解:有N个员工,就配有2N-1个管理者,用空间换时间{ int l,r,lc,rc,c;// 管理的员工编号是第l个到第r个(连续),lc表示左副经理的编号,rc表示右副经理是谁 //c表示第l个员工至第r个员工的特征值:可以是和、最大值、或者最小值}tr[410000]; int len;//tr数组就是管理者数组,len表示当前申请到第几个管理者//到此,员工有自己的编号,管理者也有自己的编号int mymax(int x,int y){ return x>y?x:y;} //求x和y最大值的函数void bt(int l,int r) // build tree建立线段树,申请一个管理者,管理第l个员工至第r个员工{ len++; int now=len;// now记录当前管理者的编号 tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1;tr[now].c=0;//一开始当前管理者now的左右副总经理都是没人-1,管理者管理范围最大值为0,这里五个元素都要赋值 if(l<r) //如果管的人大于1人,就有权申请两个副总经理帮忙管人 { int mid=(l+r)/2; // mid为l和r的中间值,从中间分为两段[l,mid]和[mid+1,r] tr[now].lc=len+1; bt( l , mid ); // [l,mid]给左副总管,让他先去管好[l,mid] tr[now].rc=len+1; bt( mid+1 , r ); // [mid+1,r]给右副总管,让他先去管好[mid+1,r] }}void change(int now,int x,int k)//change的功能:在当前管理者now的管理范围内,把管第x个员工的管理者(不知道该管理者的编号)的值改为k//理解:为什么第x个一定在now的管理范围内呢?//注意:修改,改的是管理者,不是改员工,员工已经没用了。{ if( tr[now].l==tr[now].r) { tr[now].c=k;return ;}//如果now只管一人,那么这个人的编号一定是x,为什么? int lc= tr[now].lc, rc=tr[now].rc;//找出now的左右副总分别是谁 int mid=( tr[now].l+ tr[now].r)/2;//找到now管理范围的中间位置 if( x<=mid) change(lc,x,k); //如果x在now的左副总的管理范围,那么修改这件事就交给左副总去做 else if( mid+1<=x) change(rc,x,k); //如果x在now的右副总的管理范围,那么修改这件事就交给右副总去做 tr[now].c= mymax( tr[ lc ] .c , tr[ rc ].c );//修改完后,注意要维护,有可能最大值发生变化了}int findmax(int now,int l,int r)//findmax的功能:在当前管理者now的管理范围内,找出第l个员工至第r个员工的最大值{ if( l== tr[now].l && tr[now].r== r) return tr[now].c;//如果now的管理范围刚好是[l,r],就不用问左右副总了 int lc= tr[now].lc, rc=tr[now].rc; int mid=( tr[now].l+ tr[now].r)/2; if( r<=mid) return findmax(lc,l,r); //[l,r]在左副总的管理范围内 else if( mid+1<=l) return findmax(rc,l,r); //[l,r]在右副总的管理范围内 else return mymax( findmax(lc,l,mid) , findmax(rc,mid+1,r) );//其他情况就是[l,r]一部分在左副总,一部分在右副总}int main(){ int n,m,i,x,y; char ss[10]; while( scanf("%d%d",&n,&m)!=EOF) { for(i=1;i<=n;i++) scanf("%d",&a[i]); len=0; bt(1,n);tr[1].c=0; //初始化len为0,一开始没有一个管理者for(i=1;i<=n;i++) change(1,i,a[i]);//初始化,把第i个位置改为a[i] for(i=1;i<=m;i++) { scanf("%s%d%d",ss,&x,&y); if(ss[0]=='Q') printf("%d\n", findmax(1, x,y) ); else change( 1, x, y); } } return 0;}
虽然用线段树容易空间(时间)超限,值得庆幸的是这道题数据比较弱啊,不会超限
二、lazy算法
poj2777
(现在的题目都那么罗嗦吗)
题目大概是这样的:
有L段线段(编号为1~L) ,一开始 全部是颜色1。有两种操作
1、C A B tt :A~B染第tt种颜色
2、P A B :询问A~B有多少种不一样的颜色。
还是要注意A有可能比B大。
这道题每次更改的是一段数值,并且是求出一段距离的颜色数量,所以这道题体现线段树更彻底。而更新次数就比上一道题大很多了,所以我们在更新时极有可能会更新超时。所以我们这里会用到lazy算法。
所谓lazy算法就是在更新时,只更新要求更新的这一段,而不更新他的手(儿)下(子)们。当我们要访问他的这个手(儿)下(子)时,我们才去更新他的值。这样我们就能减少很多更新的次数,从而减少时间。
然而这道题我有两个不同版本的代码
先看看第一个:
#include<cstring>#include<cstdio>using namespace std;struct node{ int l,r,lc,rc,c;}tr[210000]; int len;bool v[35];void bt(int l,int r) // build tree{ len++; int now=len; tr[now].l=l; tr[now].r=r; tr[now].lc=tr[now].rc=-1; if(l<r) { int mid=(l+r)/2; tr[now].lc=len+1;bt(l,mid); tr[now].rc=len+1; bt(mid+1,r); }}void wen(int now,int l,int r)//wen(问)函数的功能:把now所管理范围中第l个员工至第r个员工的颜色都在v数组里面体现为true{ if( tr[now].c>0) { v[tr[now].c]=true; return ;}// tr[now].c>0表示now管理范围的颜色是统一的,那么就不用麻烦左右副总了 int lc= tr[now].lc, rc=tr[now].rc; int mid=( tr[now].l+ tr[now].r)/2; if( r<=mid) wen(lc,l,r); //[l,r]在左副总的管理范围,这件事情就交给左副总去做 else if( mid+1<=l) wen(rc,l,r); //[l,r]在右副总的管理范围,这件事情就交给右副总去做 else //来到了这个else就是表示[l,r]有一部分在左副总,有一部分在右副总 { wen(lc, l , mid ); wen(rc, mid+1 , r ); }}void change(int now,int l,int r,int k)//change(改)函数的功能 :在now的管理范围内,把[l,r]改为第k种颜色{ if( tr[now].c==k) return ;//如果now管理的范围颜色统一,并且本来就是k,那么什么都不要做 if( tr[now].l==l&& r==tr[now].r) { tr[now].c=k;return ;} //如果刚好now的管理范围就是[l,r],那么不管now管理范围原来是什么颜色统一改就行 int lc= tr[now].lc, rc=tr[now].rc; int mid=( tr[now].l+ tr[now].r)/2; if(tr[now].c>0)//如果原来now的管理范围颜色统一,那么现在要改now的管理范围中的部分范围的颜色了 //此时now就想:我的把我原来的颜色先传给我的左右副总,因为他们还不知道他们现在的颜色(在这次改之前的颜色) { //这个步骤我们称为:继承 tr[lc].c= tr[now].c; tr[rc].c= tr[now].c; } if( r<=mid) change(lc,l,r,k); //如果[l,r]在左副总的管理范围中,那么这件事情就交给左副总 else if( mid+1<=l) change(rc,l,r,k); //如果[l,r]在右副总的管理范围中,那么这件事情就交给右副总 else { change(lc, l , mid , k ); change(rc, mid+1 , r , k ); } //注意:有 修改 就配带有 维护 if( tr[lc].c==tr[rc].c&& tr[lc].c>0 )tr[now].c= tr[ lc ] .c ; else tr[now].c=-1;}int main(){ int k,j,i,x,y,L,t,M,ans; char ss[10]; while( scanf("%d%d%d",&L,&t,&M)!=EOF) { len=0; bt(1,L);tr[1].c=1;//初始化所有颜色都为1 for(i=1;i<=M;i++) { scanf("%s",ss); if(ss[0]=='C') { scanf("%d%d%d",&x,&y,&k); if( x>y) { int tt=x;x=y;y=tt;} change(1,x,y,k); } else { scanf("%d%d",&x,&y); if(x>y) { int tt=x;x=y;y=tt; } //这是个坑 memset(v,false,sizeof(v)); //一开始所有颜色都没有出现过 wen(1,x,y); ans=0; for(j=1;j<=t;j++) if( v[j]==true) ans++; printf("%d\n",ans); } } } return 0;}
相信大多数人接触的都是以上这种版本。
据说这个版本在遇到一些数据时会有bug
下面推荐另外一个版本:
通过二进制的方式来记录有多少种颜色
每次更新时只要左移c-1位就可以了
在统计时就只用查找有多少个1就可以了
那!么!
如果我们用了二进制,在更新他的上(父)司(亲)时有多少种颜色是就不能用max了
我们怎么写呢???
我们知道在最后时是统计他有多少个1
并且如果他任意一个儿子有这种颜色,即使他的另外一个儿子没有,他也有这种颜色
而或(|)运算正好可以满足!
推荐并提供我的代码:
#include <cstdio>#include <cstdio>using namespace std;int len,a[100010];struct node{ int lc,rc,l,r,c;bool update;//update:false为已更新了他的儿子,true为还未更新他的儿子//c:是颜色的状态压缩值,用二进制位表示是否选用了某种颜色}tr[200010];int br(int l,int r){ len++;int now=len; tr[now].l=l;tr[now].r=r;tr[now].c=1;//与上题不一样的:由于开始整条都是颜色1,所以状态值处置设置为1,参考二进制的表示 tr[now].update=false; if (l<r) { int mid=(l+r)>>1; tr[now].lc=br(l,mid); tr[now].rc=br(mid+1,r); } return now;}void swap(int &a,int &b){ int t=a;a=b;b=t;}int col(int x)//统计x状态二进制位1的个数{ int ans=0; while (x>0) { ans+=x%2; x=x/2; } return ans;}void update(int x)//更新他的儿子。注意这一步一定要在访问他的儿子之前做好{ tr[x].update=false; tr[tr[x].lc].update=true; tr[tr[x].lc].c=tr[x].c; tr[tr[x].rc].update=true; tr[tr[x].rc].c=tr[x].c;}void change(int x,int l,int r,int c)//返回值为该线段的编号{ if (tr[x].l==l&&tr[x].r==r) { tr[x].c=1<<(c-1); //标记状态值二进制右起第c位为1,其他为0//若整段匹配,则更新该线段update标记 return ; } int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc; if (tr[x].update) update(x);//在访问儿子之前必须更新儿子,切记!!也是该算法的重点。 if (r<=mid) change(lc,l,r,c); else if (l>=mid+1) change(rc,l,r,c); else {change(lc,l,mid,c);change(rc,mid+1,r,c);} tr[x].c=(tr[lc].c|tr[rc].c);//合并左右儿子的状态。使用“或”操作计算}int findmax(int x,int l,int r){ if (tr[x].l==l&&tr[x].r==r) return tr[x].c; int mid=(tr[x].l+tr[x].r)>>1,lc=tr[x].lc,rc=tr[x].rc; if (tr[x].update) update(x); //在访问儿子之前必须更新儿子 if (l>=mid+1) return findmax(rc,l,r); else if (r<=mid) return findmax(lc,l,r); else return (findmax(lc,l,mid)|findmax(rc,mid+1,r));//返回左右儿子合并值}int main(){ int n,m,t,x,y,z; char c; scanf("%d%d%d",&n,&t,&m); br(1,n);len=0; for (int i=1;i<=m;i++) { getchar(); scanf("%c",&c); if (c=='C') { scanf("%d%d%d",&x,&y,&z); if (x>y) swap(x,y); change(1,x,y,z); } if (c=='P') { scanf("%d%d",&x,&y); if (x>y) swap(x,y);//本题坑点之一,必须注意大小关系 printf("%d\n",col(findmax(1,x,y))); } } return 0;}
上面讲了优化时间的lazy算法,下面来讲讲优化空间的离散化。
poj2528
所谓离散化就是
原数组为A[]={3,100,9845587},
这个数组那么大,但是却只用到了很少一部分
而 {1~2,4~99,…}都没有用到
我们怎样能高效地利用这些空间呢
那么我们就要用到离散化 了
离散化为S[]={1,2,3}
再比如:
原数组为A[]={ 3 , 100 , 9845587 , 6 , 6 , 9 , 11 , 2 },
离散化为S[]={ 2 , 6 , 7 , 3 , 3 , 4 , 5 , 1 }
离散化的过程:
1:原数组A每个数还得多带点东西:头x(原来的值),胸口p(原来的位置),肚子z(离散化的值)
2:把A复制一份为B,然后B数组根据B中x的值进行排序,然后根据B排序后的位置赋予每个B的离散值
3:让B中的每个元素带着自己的离散值和位置值去赋值A数组中的z值。A[ B[i].p ] = B[i].z ;
4:到此离散化结束,A.z就是A.x的离散值,而且一一对应。
值得要注意到是:
原数组:{1,2,4,5,7}
代码:
#include<cstdio>#include<cstdlib>#include<cstring>struct node{int x,y,p;};//离散化用node a[20010],b[20010];int n,mm;struct tree{int l,r,lc,rc,c;bool update;};tree tr[40010];int len=0;bool col[20010];void qsort(int l,int r)//快排{ int i=l,j=r; node mid,t; mid=b[(l+r)>>1]; while (i<=j) { while (b[i].x<mid.x) i++; while (b[j].x>mid.x) j--; if (i<=j) { t=b[i];b[i]=b[j];b[j]=t; i++;j--; } } if (l<j) qsort(l,j); if (i<r) qsort(i,r);}int bt(int l,int r){ len++; int now=len; tr[now].l=l; tr[now].r=r; tr[now].c=0; tr[now].lc=-1; tr[now].rc=-1; tr[now].update=false; if (l+1!=r) { int mid; mid=(l+r)>>1; tr[now].lc=bt(l,mid); tr[now].rc=bt(mid,r); } return now;}void update(int x)//lazy算法{ tr[x].update=false; if (tr[x].lc!=-1 && tr[x].rc!=-1) { tr[tr[x].lc].update=tr[tr[x].rc].update=true; tr[tr[x].lc].c=tr[tr[x].rc].c=tr[x].c; }}void insert(int x,int l,int r,int p)//change{ if (tr[x].l==l && tr[x].r==r) { tr[x].c=p; tr[x].update=true; return; } if (tr[x].update) update(x); int mid; mid=(tr[x].l+tr[x].r)>>1; if (r<=mid) insert(tr[x].lc,l,r,p); else if (l>=mid) insert(tr[x].rc,l,r,p); else {insert(tr[x].lc,l,mid,p);insert(tr[x].rc,mid,r,p);};}int main(){ int m; scanf("%d",&m); for (int u=1;u<=m;u++) { scanf("%d",&n);n=n<<1; for (int i=1;i<=n;i+=2) { scanf("%d %d",&a[i].x,&a[i+1].x); a[i+1].x++; a[i].p=i; a[i+1].p=i+1; } for (int i=1;i<=n;i++) b[i]=a[i]; qsort(1,n); b[1].y=1; for (int i=2;i<=n;i++) if (b[i].x==b[i-1].x) b[i].y=b[i-1].y; else b[i].y=b[i-1].y+1; for (int i=1;i<=n;i++) { a[b[i].p].y=b[i].y; } len=0; bt(1,b[n].y); for (int i=1;i<=n;i+=2) { insert(1,a[i].y,a[i+1].y,i); } memset(col,false,sizeof(col)); for (int i=1;i<=len;i++) { if (tr[i].update) update(i); if (tr[i].lc==-1) col[tr[i].c]=true; } int ans=0; for (int i=1;i<=n;i++) if (col[i]) ans++; printf("%d\n",ans); }}
今天测量一套题……结果气到我爆炸
明天将会分享
- 线段树(lazy算法+离散化)
- hdu(4325)线段树+离散化+lazy
- 【线段树(线段离散化+lazy)】poj 2528 Mayor's posters
- 线段树中的离散化与lazy标记思想
- poj2528 Mayor's posters 线段树离散化+lazy标记
- POJ 2528 (Mayor's posters)(线段树+离散化+lazy思想)
- 线段树入门 以HDU 4325为例(未离散化,也没lazy标记)
- HDU 4325线段树+离散化(排序后去重)+lazy标记
- 见微知著--POJ2528(线段树及LAZY思想+离散化思想)
- 线段树+离散化
- 离散化 + 线段树
- 线段树离散化
- 线段树离散化
- 线段树优化 lazy算法 poj3468
- hihoCoder - 1079 - 离散化 (线段树 + 离散化)
- hihoCoder 1079 离散化(线段树离散化)
- HIHO #1079 : 离散化(线段树+离散化)
- PKU 2528 (线段树 + 离散化)
- Apache 配置虚拟主机之3--基于IP+Port结合
- 关于在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种
- 【Leetcode】482. License Key Formatting
- supervivi其实挺好用的啊啊啊啊
- PLS-00905: 对象 SCOTT.QUERYEMPINFO 无效;PL/SQL: Statement ignored
- 线段树(lazy算法+离散化)
- lightOJ 1028 Trailing Zeroes (I)
- xcode问题及解决
- 线程中Sleep()、Wait()区别
- PHP语言中include 与require的区别
- JavaScript网页特效(一)图片放大镜
- Java Clone方法原理
- JAVA入门__02
- ASP.NET Core + Angular 2 Template for Visual Studio