题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1698
区间更新的简单思想:
区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定不是O(lgn),例如当我们要更新区间[0,3]内的叶子节点时,需要更新出了叶子节点3,9外的所有其他节点。为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。
延迟标记:每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。
因此需要在线段树结构中加入延迟标记域,本文例子中我们加入标记与addMark,表示节点的子孙节点在原来的值的基础上加上addMark的值,同时还需要修改创建函数build 和 查询函数 query,修改的代码用红色字体表示,其中区间更新的函数为update,
引用博文链接 http://www.cppblog.com/zhangwangcz/archive/2011/05/04/145697.html
题意: n个钩子连在一起, 初始重量都为铜 即1, 之后修改 一段区间 的重量为2 或者 3 或者 1
求总重量
我的代码:
#include <cstdio> #include <cstdlib> #include <cstring> #include <cctype> #include <cmath> #include <algorithm> using namespace std; const int N = 100005; struct tree { int left, right, mid; //区间为[left, right],中间值为mid int add; //加减标记 int value; //区间和 void init() { //value = 1; add = 0; mid = (left + right) >> 1; } }; tree node[N<<2]; int n, m; //区间为[1,n],m个询问 inline int LL(int x) { return x << 1; } inline int RR(int x) { return x << 1 | 1; } void BuildTree(int left, int right, int x); //建立线段树,区间[left,right],从节点编号为x处开始 int Query(int left, int right, int x); //查询区间[left,right]的和,从节点编号为x处开始 void Update(int left, int right, int change, int x); //区间[left,right]值增加change,从x处开始 void PushUp(int x); //向上更新 void PushDown(int x); //向下更新 int main() { int a, b, c, t, k = 0; scanf("%d", &t); while(t--) { scanf("%d %d", &n, &m); BuildTree(1, n, 1); for (int i = 0; i < m; ++i) { scanf("%d %d %d", &a, &b, &c); Update(a, b, c, 1); } if(m == 0) printf("Case %d: The total value of the hook is %d. ", node[1].value); else printf("Case %d: The total value of the hook is %d. ", ++k, Query(1, n, 1)); } return 0; } void BuildTree(int left, int right, int x) { node[x].left = left; node[x].right = right; node[x].init(); if (left == right) { node[x].value = 1; return; //到了最底层节点就直接返回,否则建子树 } BuildTree(left, node[x].mid, LL(x)); BuildTree(node[x].mid + 1, right, RR(x)); node[x].value = node[LL(x)].value + node[RR(x)].value; return; } int Query(int left, int right, int x) { if (node[x].add != 0) PushDown(x); //向下更新 if (node[x].left == left && node[x].right == right) { return node[x].value; } if (left > node[x].mid) //如果查询区间在右子树区间 { return Query(left, right, RR(x)); } else if (right <= node[x].mid) //如果查询区间在左子树区间 { return Query(left, right, LL(x)); } return Query(left, node[x].mid, LL(x)) + Query(node[x].mid + 1, right, RR(x)); //查询区间跨越左右子树区间的情况 } void Update(int left, int right, int change, int x) { if(node[x].add == change) return; if (node[x].add != 0) PushDown(x); if (node[x].left == left && node[x].right == right) { node[x].add = change; node[x].value = change * (right - left + 1); //有add标记的节点表示该节点value已经被更新但是子节点未更新 PushUp(x); return; } if (left > node[x].mid) //如果更新区间在右子树 { Update(left, right, change, RR(x)); } else if (right <= node[x].mid) //如果更新区间在左子树 { Update(left, right, change, LL(x)); } else //更新区间跨越左右子树的情况 { Update(left, node[x].mid, change, LL(x)); Update(node[x].mid + 1, right, change, RR(x)); } return; } void PushUp(int x) { while (x != 1) { x >>= 1; node[x].value = node[LL(x)].value + node[RR(x)].value; } return; } void PushDown(int x) { if (node[x].left == node[x].right) { node[x].add = 0; return; } node[LL(x)].add = node[x].add; //更新左子树 node[LL(x)].value = node[x].add * (node[LL(x)].right - node[LL(x)].left + 1); node[RR(x)].add = node[x].add; //更新右子树 node[RR(x)].value = node[x].add * (node[RR(x)].right - node[RR(x)].left + 1); node[x].add = 0; //清空父节点 return; }
别人代码,清晰易懂, 时间快
#include <stdio.h> #include <iostream> using namespace std; #define maxn 100005 struct Tree { int left, right, num; //num 为 -1,表示这个区间内不纯 }node[4 * maxn]; void BuildTree(int i, int l, int r); void Update(int i, int num, int l, int r); int Search(int i); int main() { int t, num, n, m, k = 0, l, r; scanf("%d", &t); while(t--) { scanf("%d %d", &n, &m); BuildTree(1, 1, n); while(m--) { scanf("%d %d %d", &l, &r, &num); Update(1, num, l, r); } printf("Case %d: The total value of the hook is %d. ", ++k, Search(1)); } return 0; } void BuildTree(int i, int l, int r) { node[i].left = l; node[i].right = r; node[i].num = 1; //初始化每个区间都是 纯1 的; if(node[i].left == node[i].right) return; int mid = (node[i].left + node[i].right) >> 1; BuildTree(i << 1, l, mid); BuildTree(i << 1 | 1, mid + 1, r); } void Update(int i, int num, int l, int r) { if(node[i].num == num) return; if(node[i].left == l && node[i].right == r) { node[i].num = num; return; } if(node[i].num != -1)//这段区间是 纯 的,我们要修改这个区间,则这个区间就不 纯了 { node[i << 1].num = node[i << 1 | 1].num = node[i].num; node[i].num = -1;//这个区间不纯了 } int mid = (node[i].left + node[i].right) / 2; if(r <= mid) Update(i << 1, num, l, r); else if(l > mid) Update(i << 1 | 1, num ,l, r); else { Update(i << 1, num, l, mid); Update(i << 1 | 1, num, mid + 1, r); } } int Search(int i) { if(node[i].num != -1) //即这段区间是纯的, 则不需要向下查询,直接根据线段树的特点算出 { return node[i].num * (node[i].right - node[i].left + 1); } else return Search(i << 1) + Search(i << 1 | 1); }
还有一种更快的方法:判断每个点是否在某更新区间,在则更新,不在不处理,因为前面的会被后面的覆盖,所以我们从后面考虑,倒着做。:
#include <iostream> using namespace std; int data[100005][3]; int main() { int t,q,n,i,j,sum,k,v; scanf("%d",&t); for(i=1;i<=t;i++) { scanf("%d%d",&n,&q); for(j=1;j<=q;j++) scanf("%d%d%d",&data[j][0],&data[j][1],&data[j][2]); sum=0; for(k=1;k<=n;k++) { v=1; for(j=q;j>=1;j--) if(data[j][0]<=k && k<=data[j][1])//寻找k所在的更新区间,若存在则更新,不存在v=1不变 { v=data[j][2]; //若找的最后面的更新区间,则停止,因为前面的会被覆盖 break; } sum+=v; } printf("Case %d: The total value of the hook is %d. ",i,sum); } return 0; }