浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925
Posted nopartyfoucaodong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925相关的知识,希望对你有一定的参考价值。
今天我们说说线段树。
我个人还是非常欣赏这种数据结构的。(逃)因为它足够优美,有递归结构,有左子树和右子树,还有二分的思想。
emm这个文章打算自用,就不写那些基本的操作了...
1° 简单的懒标记(仅含加法)
当我们进行区间修改(比如同时加上一个数)时,我们现在也许暂时不用它,可以当需要用的时候再改。这个时候我们就需要做个标记,这个标记就是懒标记,$lazy$。如果在后续的指令中需要从p向下递归,我们这时候检查它是否有标记。若有,就按照标记更新两个子节点,同时为子节点增加标记,清除p的标记。
比如最简单的区间修改(加法)
void spread(int p) {
if(t[p].l==t[p].r) return ; t[p*2].val+=t[p].lazy*(t[p*2].r-t[p*2].l+1); t[p*2+1].val+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1); //标记释放威力 t[p*2].lazy+=t[p].lazy; t[p*2+1].lazy+=t[p].lazy; //标记下传 t[p].lazy=0; //清空自身标记 }
也就是说,其实标记是为自己的儿子们准备的,而自己已经修改了。当自己的儿子接手了标记的衣钵,父亲也就不需要存在标记的。
- 我的习惯:$spread$常写,函数内判断条件苛刻。
- 一个习惯的change函数写法:
-
-
void change(int p,int l,int r,int op) {//op==1 I have rooms! //op==2 I lose rooms! spread(p); //标记下放 if(t[p].l==l&&t[p].r==r)//边界判断 { if(op==1) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+1; else t[p].lmax=t[p].rmax=t[p].sum=0; t[p].lazy=op;//这里也有懒标记更新 return ; } int mid=(t[p].l+t[p].r)>>1; if(l>mid) change(p*2+1,l,r,op); else if(r<=mid) change(p*2,l,r,op);//标记判断 else change(p*2,l,mid,op),change(p*2+1,mid+1,r,op);
//更新 renew(p); }
-
-
例题1 【模板】线段树 1
裸的懒标记应用。
1 #include<cstdio> 2 #include<algorithm> 3 #define maxn 100090 4 5 using namespace std; 6 typedef long long ll; 7 8 int n,m; 9 int a[maxn]; 10 struct SegmentTree{ 11 int l,r; 12 ll lazy,val; 13 }t[maxn*4]; 14 15 void build(int p,int l,int r) 16 { 17 t[p].l=l,t[p].r=r; 18 if(l==r) 19 { 20 t[p].val=a[l]; 21 return ; 22 } 23 int mid=(l+r)>>1; 24 build(p*2,l,mid); 25 build(p*2+1,mid+1,r); 26 t[p].val=t[p*2].val+t[p*2+1].val; 27 } 28 29 void spread(int p) 30 { 31 if(t[p].l==t[p].r) return ; 32 t[p*2].val+=t[p].lazy*(t[p*2].r-t[p*2].l+1); 33 t[p*2+1].val+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1); 34 t[p*2].lazy+=t[p].lazy; 35 t[p*2+1].lazy+=t[p].lazy; 36 t[p].lazy=0; 37 } 38 39 void change(int p,int l,int r,int k) 40 { 41 spread(p); 42 if(t[p].l==l&&t[p].r==r) 43 { 44 t[p].val+=k*(r-l+1); 45 t[p].lazy+=k; 46 return ; 47 } 48 int mid=(t[p].l+t[p].r)>>1; 49 if(l>mid) change(p*2+1,l,r,k); 50 else if(r<=mid) change(p*2,l,r,k); 51 else change(p*2,l,mid,k),change(p*2+1,mid+1,r,k); 52 t[p].val=t[p*2].val+t[p*2+1].val; 53 } 54 55 ll ask(int p,int l,int r) 56 { 57 spread(p); 58 if(t[p].l==l&&t[p].r==r) return t[p].val; 59 int mid=(t[p].l+t[p].r)>>1; 60 if(l>mid) return ask(p*2+1,l,r); 61 else if(r<=mid) return ask(p*2,l,r); 62 else return ask(p*2,l,mid)+ask(p*2+1,mid+1,r); 63 } 64 65 int main() 66 { 67 scanf("%d%d",&n,&m); 68 for(int i=1;i<=n;i++) 69 scanf("%d",&a[i]); 70 build(1,1,n); 71 for(int i=1;i<=m;i++) 72 { 73 int opt=0; 74 scanf("%d",&opt); 75 if(opt==1) 76 { 77 int x=0,y=0,k=0; 78 scanf("%d%d%d",&x,&y,&k); 79 change(1,x,y,k); 80 } 81 else if(opt==2) 82 { 83 int x=0,y=0; 84 scanf("%d%d",&x,&y); 85 printf("%lld ",ask(1,x,y)); 86 } 87 } 88 return 0; 89 }
By hzwer
题解
线段树
每个节点记录该段最长连续长度
为了合并还要记录坐标开始的连续长度,右边开始的连续长度
这里用到了线段树中另一个常见的思想。平常我们用线段树大多都是在维护一个值,而遇到一些复杂的信息需要维护时,我们就很难纯粹地加加减减,那么我们不妨换一种思路,多维护一些信息。最早应用这个思想的是最大子段和的维护,详情戳。(当时我还在$tsoi$讲过内qwq)就是多维护了$lmax$,$rmax$。这样父亲线段的最值可以由左儿子的$val$、右儿子的$val$、左儿子的$rmax$加右儿子的$lmax$更新维护来。
那么回到本题:一句话题意就是在维护,最大连续空房。综合之前分析,知道如何用懒标记还有知道需要维护哪些信息后,这道题就比较简单了。
$Code$
1 #include<cstdio> 2 #include<algorithm> 3 #define maxn 50090 4 5 using namespace std; 6 7 int n,m; 8 struct SegmentTree{ 9 int l,r; 10 int lazy; 11 int rmax,lmax,sum; 12 }t[maxn*4]; 13 14 void build(int p,int l,int r) 15 { 16 t[p].l=l,t[p].r=r; 17 t[p].lmax=t[p].rmax=t[p].sum=r-l+1; 18 if(l==r) return ; 19 int mid=(l+r)>>1; 20 build(p*2,l,mid); 21 build(p*2+1,mid+1,r); 22 } 23 24 void spread(int p) 25 { 26 if(t[p].l==t[p].r) return ; 27 if(t[p].lazy==2) 28 { 29 t[p*2].lazy=t[p*2+1].lazy=2; 30 t[p*2].lmax=t[p*2].rmax=t[p*2+1].lmax=t[p*2+1].rmax=0; 31 t[p*2].sum=t[p*2+1].sum=0; 32 } 33 else if(t[p].lazy==1) 34 { 35 t[p*2].lazy=t[p*2+1].lazy=1; 36 t[p*2].lmax=t[p*2].rmax=t[p*2].sum=t[p*2].r-t[p*2].l+1; 37 t[p*2+1].lmax=t[p*2+1].rmax=t[p*2+1].sum=t[p*2+1].r-t[p*2+1].l+1; 38 } 39 t[p].lazy=0; 40 } 41 42 void renew(int p) 43 { 44 if(t[p*2].sum==t[p*2].r-t[p*2].l+1) 45 t[p].lmax=t[p*2].r-t[p*2].l+1+t[p*2+1].lmax; 46 else t[p].lmax=t[p*2].lmax; 47 if(t[p*2+1].sum==t[p*2+1].r-t[p*2+1].l+1) 48 t[p].rmax=t[p*2+1].r-t[p*2+1].l+1+t[p*2].rmax; 49 else t[p].rmax=t[p*2+1].rmax; 50 t[p].sum=max(max(t[p*2].sum,t[p*2+1].sum),t[p*2].rmax+t[p*2+1].lmax); 51 } 52 53 void change(int p,int l,int r,int op) 54 {//op==1 I have rooms! 55 //op==2 I lose rooms! 56 spread(p); 57 if(t[p].l==l&&t[p].r==r) 58 { 59 if(op==1) t[p].lmax=t[p].rmax=t[p].sum=t[p].r-t[p].l+1; 60 else t[p].lmax=t[p].rmax=t[p].sum=0; 61 t[p].lazy=op; 62 return ; 63 } 64 int mid=(t[p].l+t[p].r)>>1; 65 if(l>mid) change(p*2+1,l,r,op); 66 else if(r<=mid) change(p*2,l,r,op); 67 else change(p*2,l,mid,op),change(p*2+1,mid+1,r,op); 68 renew(p); 69 } 70 71 int ask(int p,int len) 72 { 73 spread(p); 74 int mid=(t[p].l+t[p].r)>>1; 75 if(t[p].l==t[p].r) return t[p].l;//找到真正精确的地方了 76 if(t[p*2].sum>=len) return ask(p*2,len); 77 //左面就已经有足够空房 继续向下找更小更精细的 78 else if(t[p*2].rmax+t[p*2+1].lmax>=len) return mid-t[p*2].rmax+1; 79 //跨越边界的部分有足够空房 80 else return ask(p*2+1,len); 81 //否则只能去右子树找连续空房 82 } 83 84 int main() 85 { 86 scanf("%d%d",&n,&m); 87 build(1,1,n); 88 for(int i=1;i<=m;i++) 89 { 90 int opt=0; 91 scanf("%d",&opt); 92 if(opt==1) 93 { 94 int x=0; 95 scanf("%d",&x); 96 if(t[1].sum<x){printf("0 ");continue;} 97 int tmp=ask(1,x); 98 printf("%d ",tmp); 99 change(1,tmp,tmp+x-1,2); 100 } 101 else if(opt==2) 102 { 103 int x=0,y=0; 104 scanf("%d%d",&x,&y); 105 change(1,x,x+y-1,1); 106 } 107 } 108 return 0; 109 }
未完待续.
以上是关于浅谈线段树 (例题:[USACO08FEB]酒店Hotel)By cellur925的主要内容,如果未能解决你的问题,请参考以下文章