题目链接 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.\n", node[1].value);
else
printf("Case %d: The total value of the hook is %d.\n", ++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.\n", ++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.\n",i,sum);
}
return 0;
}