首页 > 学院 > 开发设计 > 正文

线段树标记永久化个人理解 & BZOJ 1513 [POI2006]Tet-Tetris 3D

2019-11-08 19:49:01
字体:
来源:转载
供稿:网友

Description Task: Tetris 3D “Tetris” 游戏的作者决定做一个新的游戏, 一个三维的版本, 在里面很多立方体落在平面板,一个立方体开始落下直到碰上一个以前落下的立方体或者落地即停止. 作者想改变一下游戏的目的使得它更大众化,在新游戏中你将知道落下的立方体信息以及位置,你的任务就是回答所有立方体落下后最高的方块的高度.所有的立方体在下落过程中都是垂直的并且不会旋转.平板左下角坐标为原点,并且平行于坐标轴. Input 第一行给出三个整数 D, S and N ( 1<= N<= 20 000, 1<= D, S <=1 000), 分别表示平板的长和宽以及下落立方体的数目. 接下来N 行每行描述一个立方体. 每行包含5个整数: d, s, w, x and y (1<= d, 0 <=x, d + x<= D, 1 <=s, 0<= y, s + y<= S, 1<= w <=100 000), 分别表示立方体的长/宽/高以及落下的左下角坐标, 长和宽都是平行于平板坐标轴的,落下后立方体着地的四个角坐标分别为: (x, y), (x + d, y), (x, y + s) and (x + d, y + s). Output 一个整数表示所有立方体落下后最高的方块的高度. Sample Input 7 5 4 4 3 2 0 0 3 3 1 3 0 7 1 2 0 3 2 3 3 2 2 Sample Output 6


将x与y看作一个二维平面

每个立方体的下落可以看作查询矩阵 (x+1,y+1) (x+d,y+s) 内的最大值并将矩阵内所有值变为maxh+w

考虑树套树(二维线段树)的做法 我们发现标记下传(pushdown)与信息上传(pushup)很难办到

这时标记永久化派上用场了

那标记永久化是个啥呢?

在写普通线段树时 我们对修改的区间打上标记 并在查询的时候下放标记并更新答案

那么我们是否可以不对标记进行下放 在路过该节点(需要用到它或它儿子的信息)的时候把修改对答案的影响加上 来省去标记下放的过程呢?

吼哇!

于是我们yy了一个新的不用pushdown的线段树

新线段树的每个节点维护 sum 与 all 两个标记

分别代表 区间和 与 节点所表示的区间都被加了多少

sum也可以看作该节点所表示的区间内有一些地方被加上了一些值 它们的和为sum(具体位置我们不关心)

修改操作就相当于一路更新所经过节点的sum值 直到目标区间与当前结点区间完全覆盖 更新该点的all值

查询操作与修改操作正好相反 计算所经过节点的all值对答案的贡献 直到目标区间与当前结点区间完全覆盖 并用该节点的sum值更新答案

上几个图理解一下吧:

标记永久化1 标记永久化2 标记永久化3 标记永久化4

/* 区间修改 区间求和 l,r为当前节点表示的区间 nl,nr为目标区间*/#define LL long long#define ls (cnt<<1)#define rs (cnt<<1|1)LL n,m;LL sum[MAXN*4],all[MAXN*4],w[MAXN];void build(LL l,LL r,LL cnt){ if(l==r){sum[cnt]=w[l];return;} LL mid=(l+r)>>1; build(l,mid,ls),build(mid+1,r,rs); sum[cnt]=sum[ls]+sum[rs];}LL query(LL l,LL r,LL nl,LL nr,LL cnt){ LL add=(nr-nl+1)*all[cnt],mid=(l+r)>>1;//计算标记对答案的影响 if(l==nl&&r==nr) return add+sum[cnt]; if(nr<=mid) return add+query(l,mid,nl,nr,ls); if(nl>=mid+1) return add+query(mid+1,r,nl,nr,rs); return add+query(l,mid,nl,mid,ls)+query(mid+1,r,mid+1,nr,rs);}void update(LL l,LL r,LL nl,LL nr,LL v,LL cnt){ if(l==nl&&r==nr){all[cnt]+=v;return;}//当前结点表示的区间与目标区间完全重合 更新标记(all)值 LL mid=(l+r)>>1; if(nr<=mid) update(l,mid,nl,nr,v,ls); else if(nl>=mid+1) update(mid+1,r,nl,nr,v,rs); else update(l,mid,nl,mid,v,ls),update(mid+1,r,mid+1,nr,v,rs); sum[cnt]+=(nr-nl+1)*v;//更新区间sum}int main(){ n=read(),m=read(); for(LL i=1;i<=n;i++) w[i]=read(); build(1,n,1); while(m--) { LL t=read(),a=read(),b=read(),c; if(t==1) c=read(),update(1,n,a,b,c,1); else PRintf("%lld/n",query(1,n,a,b,1)); } return 0;}

相当于我们用all标记来代替了标记下传的过程 因为当查询到该节点及以下节点时 必定会用该节点的all值来更新答案

sum则代替了信息上传的过程 维护该节点所表示区间的信息 而信息上传已经在我们进行修改操作的时候顺便做了

因此不再需要信息上传(pushup)与标记下传(pushdown)两个操作

回到本题 对于第一维 我们也可以维护这样的两个值 all 与 have

分别代表 该区间全变成了all

与该区间内有一些点变为了have

然后第二维用 普通 or 标记永久化 线段树来维护这两个值即可。

#include <cstdio>#include <cstring>#include <cmath>#include <algorithm>#define MAXN 2505#define LL long longusing namespace std;int read(){ int f=1,t=0; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();} return t*f;}/*----- head -----*/#define ls (cnt<<1)#define rs ((cnt<<1)+1)#define mid ((l+r)>>1)int n,m,p;struct trey{ int all[MAXN],have[MAXN]; void update(int l,int r,int y1,int y2,int v,int cnt) { if(l==y1&&r==y2){all[cnt]=max(all[cnt],v);return;} have[cnt]=max(have[cnt],v); if(y2<=mid) update(l,mid,y1,y2,v,ls); else if(y1>mid) update(mid+1,r,y1,y2,v,rs); else update(l,mid,y1,mid,v,ls),update(mid+1,r,mid+1,y2,v,rs); } int query(int l,int r,int y1,int y2,int cnt) { int ans=all[cnt]; if(l==y1&&r==y2) return max(ans,have[cnt]); if(y2<=mid) return max(ans,query(l,mid,y1,y2,ls)); if(y1>mid) return max(ans,query(mid+1,r,y1,y2,rs)); ans=max(ans,query(l,mid,y1,mid,ls)); ans=max(ans,query(mid+1,r,mid+1,y2,rs)); return ans; }};struct trex{ trey have[MAXN],all[MAXN]; void update(int l,int r,int x1,int x2,int y1,int y2,int v,int cnt) { if(l==x1&&r==x2){all[cnt].update(1,m,y1,y2,v,1);return;} have[cnt].update(1,m,y1,y2,v,1); if(x2<=mid) update(l,mid,x1,x2,y1,y2,v,ls); else if(x1>mid) update(mid+1,r,x1,x2,y1,y2,v,rs); else update(l,mid,x1,mid,y1,y2,v,ls),update(mid+1,r,mid+1,x2,y1,y2,v,rs); } int query(int l,int r,int x1,int x2,int y1,int y2,int cnt) { int ans=all[cnt].query(1,m,y1,y2,1); if(l==x1&&r==x2) return max(ans,have[cnt].query(1,m,y1,y2,1)); if(x2<=mid) return max(ans,query(l,mid,x1,x2,y1,y2,ls)); if(x1>mid) return max(ans,query(mid+1,r,x1,x2,y1,y2,rs)); ans=max(ans,query(l,mid,x1,mid,y1,y2,ls)); ans=max(ans,query(mid+1,r,mid+1,x2,y1,y2,rs)); return ans; }}T;int main(){ n=read(),m=read(),p=read(); while(p--) { int d=read(),s=read(),w=read(),x=read(),y=read(),maxh; maxh=T.query(1,n,x+1,x+d,y+1,y+s,1); T.update(1,n,x+1,x+d,y+1,y+s,maxh+w,1); } printf("%d/n",T.query(1,n,1,n,1,m,1)); return 0;}

注意本题矩形内的最大值只会变的越来越大 所以信息上传时不会用到左右儿子的信息 使用标记永久化就不会出现问题

若答案并不是单调递增的

那么我们在更新答案的时候就会出现问题 因为我们并不知道标记打上的顺序是谁在前谁在后


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表