线段树学习心得 poj3468

来源:互联网 发布:小猪cms微电商系统 编辑:程序博客网 时间:2024/05/18 01:59

2016.5.10 神奇的博主回来了

今天,当然讲的就是加强版的堆,其实算线段树的说。。。

(还有你们看过的留留言好不好呀,谢谢了)

---------------------------

今天说的就是[[[加强版!!!]]]

内容大致就是成段更新,区间求和


也就是说,会用到高大上的lazy tag,那么lazy tag有多lazy呢?神奇的是,如果每次区间全都加一个数,然后每次全都从下面到顶走一遍的话,妥妥的就炸。所以说,利用lazy tag标记的话,其实就是如果有增加的话,只是做一个标签,在有必要的时候再调整,可以大幅缩小运行需要。


因为需要,博主选择一段一段的来写,当然会在结尾贴上完整代码。(所谓的最后一排/最顶上的,看不懂的话,请在上面的博文中找图)

-----------------------------


因为是一段一段的,就不加注释符了。

首先,是建树

struct Node

{

    int l,r;

    l所表示的是左子的管辖范围就是说能管的区间的最左端

    r所表示的是右子的管辖范围就是说能管的区间的最右端

    int sum2,lazy;

    sum也就是左子右子所管辖的sum的和

    lazy也就是lazy tag

    为了便于下面的阅读,我将在这里简单介绍一下<<&>>

    首先,<<代表的是二进制移位(往左移位)   >>代表的是二进制    往右移位,后面跟的数字就是移位的数。那移位又能干什么呢?比如:

2的二进制是00010是吧

移位一位     00100就是4

移位两位     01000就是8

移位三位     10000就是16

可以看出往左移位1位是乘22位是乘43位呢,就是乘8等等,说是用起来比乘法要快一些。

反之就是一次除二

 } tree[N<<2]; 不是乘2,是乘4!理论上乘3就够了,但是多一点总没坏处,只要不炸就行。


下面就是建树了。

我是潇洒哥,潇洒到根本不处理原数,直接用sum。其实最下面的sum就是原数所以其实更便捷了。

void build(int l,int r,int rt)  

这个建树时使用的是递归建树。

{

    tree[rt].l = l;

    tree[rt].r = r;  

完整程序中是build(1,n,1); 当然,不可能全都管辖范围最左端全是1,右端也不可能全是n,这就是为什么递归,当lr都是初始的1n时,那么我们在处理的一定是顶端第一个,那么顶端第一个的左端右端就是1n 

    tree[rt].lazy=0;  

lazy置空,因为还没有涉及到成段更新的问题,初始化一下

    if(l == r)

    {

如果说左端和右端一样了,那么就说明是最下面一排的了,那么就要开始输入处理了。

        scanf("%d",&sum[rt]);

输入sum,因为最后一排的管辖总和就是自己本身的数

        return ;

    }

    build(lson);

递归左子,你一定会想,三个参数为啥就一个了。。。这里容我解释一下,前面我用了宏

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

当然,前面<<>>也做了相应解释,在这里与或非中的或(|)为什么代表+1,我也不是很懂,所以如果有大神指点一下,谢谢。

2是左子,乘2+1是右子。涉及到位运算的相关知识,如果说是11,那么肯定答案还是1。但是它前面带着一个左移,那么最后一位一定是0,那么或1一定是+1。附上相关知识链接:http://baike.baidu.com/link?url=8gk3GFWZb0DQexRjbrdMjcRkept4HNaaR1EJ4HvMbgLm5COvKWmXPDoNtZFFayzKcS1mR1YnoKX_HHNd9iVTDq


    build(rson);

    PushUp(rt);

这个PushUp的作用,接下来揭晓。

}

-----------------------------

下面我会选择先把易懂的查询写完。

int query(int l,int r,int rt)

还是递归,我似乎太喜欢递归了吧。。。

{

    if(l == tree[rt].l && r == tree[rt].r)

如果说正好的话

    {

        return tree[rt].sum2; 

那答案就是当前的和

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

这个PushDown的作用,接下来揭晓。

    int res = 0;

这个res就是最后的答案。

    int m=(tree[rt].l+tree[rt].r)/2;  

m所代表的就是一个分界线

就是说,比如说从35查询

一共5个数

最开始的一定是1,所以左子是1,右子是5

m=(1+5)2=3

就是说跟快排(快速排序http://baike.baidu.com/link?url=zvoAGAi9YDykOgfvG__HCoAHVHDHoG8GP1uB9FyKwGpz3qYnRJjPPs0ZEXK1BYPtlkESo2_57WQDokkERB3tbjDLltgx4_BauWtkelIhGFbvosfCuh3vmuMAti6clzgjBrg5CvGC1AXfFS-T8EIGTb0mAzJUOFRLUvFf2WW9ex90V68KasgStbm0i1wmbrZj 

可以了解一下,也很有趣,也是从中间平均分)差不多,从中间分开一下。

    if(r <= m) res += query(l,r,rt<<1); 

 如果说右子还在m的左面,也就说所有的全部都在左边的话,那就处理左子

    else if(l > m) res += query(l,r,rt<<1|1);

如果说左子还在m的右面,也就说说有的全部都在右面的话,那就处理右子

    else

    {

        res += query(l,m,rt<<1);

        res += query(m+1,r,rt<<1|1);

有左有右的话,就求两边的和

    }

    return res;

返回结果

}

-----------------------------

因为在build里面先出现的是PushUp,所以也没什么理由,任性决定先写PushUp

void PushUp(int rt)

数据上推,也就是说(找图看)上面的数据

{

    tree[rt].sum2=tree[2*rt].sum2+tree[2*rt+1].sum2;

当前的sum就是左子右子sum的和,和上面博文中的max差不太多

}

-----------------------------

 void PushDown(int rt,int m)

下发数据,m是左右子区间长度

{

    if(tree[rt].lazy==0) return;

如果说没有要加的lazy那就省事了

    tree[rt*2].lazy+=tree[rt].lazy;

有的话,下发给左子

    tree[rt*2+1].lazy+=tree[rt].lazy;

下发给右子

    tree[rt*2].sum2+=tree[rt].lazy*(m-(m/2));

它左子加的和,一定是它能管到的数量的一半再乘上lazy

因为二叉树左子管到的一定是一半,而每个都是加lazy

但是和左子不同的是,虽然说在数学的角度,(m-(m/2))和(m/2)似乎是一样的,但是说如果是单数这样的话,就会有误差,当然误差会像滚雪球一样越滚越大,所以说要特殊处理一下。

    tree[rt*2+1].sum2+=tree[rt].lazy*(m/2);

右子不同。

    tree[rt].lazy=0;

必须清空!!!看似微小的错误往往是致命的!如果钥匙没有置空的话,就会一直循环下去!!!一定要注意啊!


}

-----------------------------

还剩一个update

void update(int c,int l,int r,int rt)

{

又是递归。。。big boss来啦,就是所谓的成段更新

首先解决三个参数,第一个就是更新所加的数值,第二个第三个就是成段更新的范围,第4rt当然就是当前位置不用多说

    if(tree[rt].l == l && r == tree[rt].r)

如果位置正好

    {

        tree[rt].lazy+=c;

当前位置的lazy直接就是更新所加的数

        tree[rt].sum2+=(int)c * (r-l+1); 

当然这个跟上面PushDown中的一模一样,那为什么还要加呢?

当然这个问题愚蠢的楼主想了一会,本来就要改代码了,发现其实如果在这种情况下根本不会调用PushDown,所以并不存在重复的情况,可以安心使用。

        return;

递归须谨慎啊!debug伤不起。。。

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

记得处理完把数据下发一下。

    int m=(tree[rt].l+tree[rt].r)/2;  

m还是分界

    if(r <= m) update(c,l,r,rt<<1);

其实这一段就跟query没什么区别啦,只不过是更新而已,判断条件都没变呢,还是自己去看吧

    else if(l > m) update(c,l,r,rt<<1|1);

    else

    {

        update(c,l,m,rt<<1);

        update(c,m+1,r,rt<<1|1);

    }

    PushUp(rt); 

上推一下数据。

}

-----------------------------

主函数非常简单,在这里也不再赘述,当然,前面所答应的完整代码是一定会放的,首先,我找到了一道poj3468,先分析一下题目:

A Simple Problem with Integers

Time Limit: 5000MS Memory Limit: 131072K

Total Submissions: 89915 Accepted: 27990

Case Time Limit: 2000MS

Description

You have N integers, A1,A2, ... ,AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

Input

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.

The second line contains N numbers, the initial values of A1,A2, ... ,AN. -1000000000 ≤Ai ≤ 1000000000.

Each of the next Q lines represents an operation.

"C a b c" means adding c to each of Aa,Aa+1, ... ,Ab. -10000 ≤c ≤ 10000.

"Q a b" means querying the sum of Aa,Aa+1, ... ,Ab.

Output

You need to answer all Q commands in order. One answer in a line.

Sample Input

10 5

1 2 3 4 5 6 7 8 9 10

Q 4 4

Q 1 10

Q 2 4

C 3 6 3

Q 2 4

Sample Output

4

55

9

15

Hint

The sums may exceed the range of 32-bit integers.

Source

POJ Monthly--2007.11.25, Yang Yi

这道题所讲的就是成段更新,区间求和。Q是区间查询,求和。C是成段更新。值得注意的是,“The sums may exceed the range of 32-bit integers.”,用int要炸,但是poj的编译系统只认__int 64Xcode中的lld似乎是不行的。

代码:

#include <iostream>

#include <cstdio>

using namespace std;

const int N = 100005;

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

struct Node

{

    int l,r;

    __int64 sum,lazy;

    int mid()

    {

        return (l+r)>>1;

    }

    

} tree[N<<2];


void PushUp(int rt)

{

    tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;

    

}


void PushDown(int rt,int m)

{

    if(tree[rt].lazy)

    {

    

    tree[rt*2].lazy+=tree[rt].lazy;

    tree[rt*2+1].lazy+=tree[rt].lazy;

    tree[rt*2].sum+=tree[rt].lazy*(m - (m>>1));

    tree[rt*2+1].sum+=tree[rt].lazy*(m>>1);

    tree[rt].lazy=0;

}

}

void build(int l,int r,int rt)

{

    tree[rt].l = l;

    tree[rt].r = r;

    tree[rt].lazy=0;

    if(l == r)

    {

        scanf("%I64d",&tree[rt].sum);

        return ;

    }

    int m=tree[rt].mid();

    build(lson);

    build(rson);

    PushUp(rt);

    

}

void update(int c,int l,int r,int rt)

{

    if(tree[rt].l == l && r == tree[rt].r)

    {

        tree[rt].lazy+=c;

        tree[rt].sum+=(int)c * (r-l+1);

        return;

    }

    if(tree[rt].l == tree[rt].r) return;

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

    int m = tree[rt].mid();

    if(r <= m) update(c,l,r,rt<<1);

    else if(l > m) update(c,l,r,rt<<1|1);

    else

    {

        update(c,l,m,rt<<1);

        update(c,m+1,r,rt<<1|1);

    }

    PushUp(rt);

}

__int64 query(int l,int r,int rt)

{

    if(l == tree[rt].l && r == tree[rt].r)

    {

        return tree[rt].sum;

    }

    PushDown(rt,tree[rt].r - tree[rt].l + 1);

    int m=tree[rt].mid();

    __int64 res = 0;

    if(r <= m) res += query(l,r,rt<<1);

    else if(l > m) res += query(l,r,rt<<1|1);

    else

    {

        res += query(l,m,rt<<1);

        res += query(m+1,r,rt<<1|1);

    }

    return res;

}

int main()

{

    int n,m;

    while(~scanf("%d %d",&n,&m)) 

//按位取反

    {

        build(1,n,1);

        while(m--)

        {

            char ch[2];

            scanf("%s",ch);

            int a,b,c;

            if(ch[0] == 'Q')

            {

                scanf("%d %d", &a,&b);

                printf("%I64d\n",query(a,b,1));

            }


            else

            {

                scanf("%d %d %d",&a,&b,&c);

                update(c,a,b,1);

            }

        }

    }

    return 0;

}


-----------------------------

再给你们点福利,两组测试数据奉上:

10 4

1 5 8 2 5 9 3 5 8 9

Q 3 6 

C 4 6 1

Q 5 8 

Q 4 5 


24

24

9

==================

29 5

2 5 2 3 6 7 3 5 8 3 7 5 4 1 2 6 8 3 6 3 2 5 7 9 8 6 3 1 5 

Q 3 19

C 1 28 5

Q 3 6 

C 3 6 8

Q 5 10


79

38

78

—————————————————————————————就这样的,然后不懂的可以留言,还有一道线段树poj3667也很适合,会在稍后奉上。



1 0
原创粉丝点击