简单易懂线段树-第一步建树-脑袋蒙的来瞧瞧

来源:互联网 发布:java字符串拼接加空格 编辑:程序博客网 时间:2024/05/16 10:04

看了网上好多好多好多的代码,能告诉我为啥都用 '>>'这些符号写的么...是感觉很好看么...(确实挺漂亮问题是本来就很蒙的好吧!)还有啊,确实是我实力太差define 定义左右儿子那每次看到函数里就蒙了,我都替换成正常的了

不过幸好在头脑清醒的时候弄明白了一些,虽然现在查询那还是不太懂

好啦好啦不多说了

首先我先把正常有加减乘除符号的代码给贴出来,当初我就是卡在这了,几乎没见到正常的代码= =本来就蒙蒙的

有这里卡住的看到就懂了就不用往下看了,用hdu1166举例子

//hdu 1166 敌兵布阵
#include<cstdio>#include<cstring>int sum[55555*4];void pushup(int rt){    sum[rt] = sum[rt*2]+sum[rt*2+1];}void build(int l, int r, int rt){    if(l == r)    {        scanf("%d", &sum[rt]);        return ;    }    int m = (l+r)/2;    build(l, m, rt*2);    build(m+1, r, rt*2+1);    pushup(rt);}void update(int p, int add, int l, int r, int rt){    if(l == r)    {        sum[rt]+=add;        return;    }    int m = (l+r)/2;    if(p <= m)update(p, add, l, m, rt*2);    else update(p, add, m+1, r, rt*2+1);    pushup(rt);}int query(int L, int R, int l, int r, int rt){    if(L <= l && r <= R)return sum[rt];    int m = (l+r)/2;    int ret = 0;    if(L <= m) ret += query(L, R, l, m, rt*2);    if(R > m)ret += query(L, R, m+1, r, rt*2+1);    return ret;}int main(){    int tt, n, i, j, k, x, y;    scanf("%d", &tt);    for(int cas = 1; cas <= tt; ++cas)    {        printf("Case %d:\n", cas);        scanf("%d", &n);        build(1, n, 1);        char ch[10];        while(scanf("%s", ch),ch[0]-'E')        {            scanf("%d%d", &x, &y);            if(ch[0]=='A')update(x, y, 1, n, 1);            else if(ch[0]=='S')update(x, -y, 1, n, 1);            else printf("%d\n",query(x, y, 1, n, 1));        }    }    return 0;}
接下来就是我个人看的理解了,有讲错了欢迎指出

1、首先既然是线段树 既然叫做‘树’ 就说明是树状的对吧,这样我们 怎么用这样的树 来记录各种值呢

答案就是 用树叶来记录每个兵营的人数(我们用这道题举例子, 大家快去读一读是中文题欸)

然后每个节点 就能记录下面叶子的和,这样一层一层的记录就好查了

可是大家还是很蒙,怎么记录,怎么查找,你光这么说我也不知道啊,思想谁都会啊!

别急,才开始,且听我用我的方式慢慢道来




2、开始建立树,函数 void build(int l, int r, int rt) 我们都需要准备什么呢? 我这里不用结构体,就用最直白的

准备: rt, 这个是记录当前结点标号的,就是每个节点的下标(index)

   l, r, 这个是 当前第 rt 节点  所记录的 左边 和 右边, 就是l到r之间的和

           在函数最外面还需要一个sum[ ]数组,用来记录每个节点记录的值

接下来是一个关键点:当l==r的时候代表什么呢?

对!当l==r 说明我们当前第rt个点只有记录了它自己,因为它下面没有了分支所以他是叶子!!

所以build函数一开始就是这样

<pre name="code" class="cpp"><pre name="code" class="cpp">void build(int l, int r, int rt){    if(l == r)//当前发现是叶子,也就是每个兵营    {        scanf("%d", &sum[rt]);//那么我就输入一个要记录的兵营人数        return;//当前找到最底部的叶子了,可以返回了    }    函数还没写完}

为什么叶子还用sum记录人数呢,你想想l==r是不是说明也是个区间和?只不过这个区间只有一个值,所以没必要开其他数组混淆视听啦

那么每次我怎么找到l == r啊?你去看主函数 ,是不是写作build(1, n, 1); n是我们兵营数量

也就是说我开始时候l从1开始,r从n开始,1到n就是 rt == 1 记录的区间

然后函数发现它不是叶子,就继续执行

void build(int l, int r, int rt){    if(l == r)    {        scanf("%d", &sum[rt]);        return;    }    int m = (l+r)/2; //把区间对半分    build(l, m, rt*2);//从l -> m 左半递归    build(m+1, r, rt*2+1);//从 m+1 -> r 右半递归    pushup(rt);}

就这样,不是叶子就对半分,然后左递归,右递归,记住每次标记rt需要*2和*2+1,这是二叉树的规律嘛,我们利用了这个规律

pushup用来每次递归已经确立的左子树和右子树的和

void pushup(int rt){    sum[rt] = sum[rt*2]+sum[rt*2+1];}

到这里建树就基本完成了,我实力太差只能写成这样了,我的想法就是理解如何写出的代码,然后用理解的方式背下来,

至少能记住很久,至于真正理解这样的思想就靠题和时间 还有 之后的返工了,我这里只是讲了怎么理解写出来的

看看有木有人看,有木有人理解,要是需要我再写添加add和查询query,其实这俩和建树异曲同工啦

我也想成为厉害的人,给自己个交代我也是能做到很厉害的人,否则一直会笼罩在没有自信之中

大家一起努力啦啦啦

最后提示一下如果用cin和cout一定会超时的,我试着用

int main(){    std::ios::sync_with_stdio(false);    std::cin.tie(0);    剩下的主函数接着写}

我试着用这两句话加速cin和cout 才勉强980多ms踩过,而且解除和scanf的绑定之后一开始输出Case什么的会报错

还需要拆开输出,很费劲,就算我不用endl也才省了20多ms(之前看贴吧有人说endl费时),所以老老实实用scanf和printf吧!

0 0
原创粉丝点击