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

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

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

好啦好啦不多说了

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

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

//hdu 1166 敌兵布阵
#include
#include

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、首先既然是线段树 既然叫做‘树’ 就说明是树状的对吧,这样我们 怎么用这样的树 来记录各种值呢

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

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

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

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

简单易懂线段树-第一步建树-脑袋蒙的来瞧瞧_第1张图片



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

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

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

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

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

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

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

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吧!

你可能感兴趣的:(暑假训练,线段树)