HDU1968 线段树区间更新

题目链接 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;
}


posted @ 2015-02-06 17:10  豪气干云  阅读(214)  评论(0编辑  收藏  举报