线段树懒惰点标记更新 hduHDU - 1698 Just a Hook
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1698
题意:自行读题
解题思想:线段树原更新一次只能更新一个叶子节点,并更新此叶子结点以上所有相关的点,当一个区间做相同更新时,叶子节点以上的相关节点不断更新,时间复杂度增加。为节省时间,为每个点添加懒惰标记。自定义节点范围(l,r为求子节点区间和),懒惰点标记(lazy储存变化值),节点和(value节点区间和)。具体实现过程:当更新一个区间时,标记该区间的父亲节点为懒惰区间(祖宗节点的值不用标记,只要更新),同时更新涵盖该区间的节点,而该区间暂时懒得去更新,等下一次更新的区间与原被标记的区间有重合时,则此时将原标记区间往下标记并更新,同时父亲节点因为更新了,没懒惰了,则取消懒惰标记,即懒惰标记传给了子节点,直到重合区间可以以子节点为单位时就可以偷懒不用往下更新了,然后以相同懒惰更新的方式更新这次要更新的区间。
样例代码图解:
下图为初始化树,圈内为value值,下标为范围。
第一次更新后1-5更新为2时后的树,红色为标记点,发现1-5的子节点并没有更新,因为计算总和只和1-5这个点有关,更新这个点就行了。
第二次更新:在访问点5-9时发现5所在的1-5点被标记了,标记点下推,并更新子节点的值。
第二次5-9区间更新后的树以及标记的点。
根据图一下子就可以看出懒惰标记的意思来了,结合代码就方便多了,我画了很久的图QAQ。
具体代码如下:
#include<bits/stdc++.h> const int maxn=100000; using namespace std; struct nodetree{ int l,r; int value,lazy; }tree[maxn<<2]; void pushup(int now) { tree[now].value=tree[now<<1].value+tree[now<<1|1].value; } void build(int l,int r,int n) { tree[n].l=l; tree[n].r=r; tree[n].lazy=0; if(l==r) { tree[n].value=1; return; } int mid=(l+r)>>1; build(l,mid,n<<1); build(mid+1,r,n<<1|1); pushup(n); } void pushdown(int n) { if(tree[n].lazy!=0) { tree[n<<1].lazy=tree[n<<1|1].lazy=tree[n].lazy; tree[n<<1].value=(tree[n<<1].r-tree[n<<1].l+1)*tree[n<<1].lazy; tree[n<<1|1].value=(tree[n<<1|1].r-tree[n<<1|1].l+1)*tree[n<<1|1].lazy; tree[n].lazy=0; } } void update(int l,int r,int le,int ri,int n,int va) { if(r<le||l>ri) return; if(l>=le&&r<=ri) { tree[n].lazy=va; tree[n].value=tree[n].lazy*(tree[n].r-tree[n].l+1); return; } pushdown(n); int mid=(l+r)>>1; if(mid>=le) update(l,mid,le,ri,n<<1,va); if(mid<ri) update(mid+1,r,le,ri,n<<1|1,va); pushup(n); } int main() { int T; while(~scanf("%d",&T)) { int count=1; while(T--) { int n,t,l,r,va; scanf("%d",&n); build(1,n,1); scanf("%d",&t); while(t--) { scanf("%d %d %d",&l,&r,&va); update(1,n,l,r,1,va); } printf("Case %d: The total value of the hook is %d.\n",count++,tree[1].value); } } return 0; }
最后说一下点的value是通过数学计算算出来的。