zoukankan      html  css  js  c++  java
  • 线段树初步(1)

    蒟蒻终于要开始好好学线段树了……

    线段树是一种二叉树形结构(二叉搜索树),属于平衡树的一种。它将线段区间组织成树形的结构,并用每个节点来表示一条线段[a,b]。每个节点的左右儿子线段分别是该线段的左半[a,(a+b)/2]和右半[(a+b)/2+1,b]区间,递归定义之后就是一棵线段树。

    1.建树

    既然线段树是递归建立的,那么我们就在递归到叶节点的时候把数据植入,之后递归返回的时候,将要修改的节点的值修改为其子节点的值之和即可。

    void build(int l,int r,int x)//递归建树,到叶节点输入,之后递归返回修改 
    {
        if(l == r)    
        {
            tree[x] = read();
            return;
        }
        int mid = (l+r) >> 1;
        build(l,mid,x << 1);
        build(mid+1,r,x << 1 | 1);
        tree[x] = tree[x << 1] + tree[x << 1 | 1];
    } 

    2.单点修改(增加或者删除)

    因为每条节点表示一条线段……所以在单点修改的时候我们只要找到需要修改的节点所在的位置,之后当当前区间l==r的时候修改这个点就可以。如果要删除,就往里面传负数就行。

    注意修改结束之后返回的时候也要……递归把其父亲和祖先的值都修改一遍。

    void update(int k,int val,int l,int r,int x)//单点修改 
    {
        if(l == r)
        {
            tree[x] += val;
            return;
        }
        int mid = (l+r) >> 1;
        if(k <= mid) update(k,val,l,mid,x << 1);
        else update(k,val,mid+1,r,x << 1 | 1);
        tree[x] = tree[x << 1] + tree[x << 1 | 1];
    }//k为要修改的节点编号,val为修改值,l,r为左右区间

    3.区间查询 

    给定一段区间,求区间中所有元素之和。

    当我们当前访问的区间其被完全包含于所求区间的时候,直接把这段区间和加上即可。

    否则的话我们进行二分,把所求区间分割成更小的区间,之后找到能被其完全包含的区间进行累加即可。(也就是相当于我们把所求区间分割成了连续的小区间之后求和)

    累加的过程同样是在递归中完成的。

    int query(int kl,int kr,int l,int r,int x)
    {
        if(kl <= l && kr >= r) return tree[x];
        int mid = (l+r) >> 1;
        int sum = 0;
        if(kl <= mid) sum += query(kl,kr,l,mid,x << 1);
        if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1);
        return sum;    
    } //kl为查询左端点,kr为查询右端点,l,r为当前区间作用端点,x为节点编号 

    会了这几个操作之后就可以做一道稍微简单一点的练习题了。

    C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,DerekTidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
    中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人

    【输入格式】

    第一行一个整数T,表示有T组数据。
    每组数据第一行一个正整数NN<=50000,表示敌人有N个工兵营地,接下来有N个正整数,i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
    接下来每行有一条命令,命令有4种形式:
    (1) Add i j,ij为正整数,表示第i个营地增加j个人(j不超过30
    (2)Sub i j ,ij为正整数,表示第i个营地减少j个人(j不超过30;
    (3)Query i j ,ij为正整数,i<=j,表示询问第i到第j个营地的总人数;
    (4)End 表示结束,这条命令在每组数据最后出现;
    每组数据最多有40000条命令

    【输出格式】

    对第i组数据,首先输出“Case i:”和回车,
    对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

     这道题为了方便我把字符串都改成了数字。

    操作就三个,建树,单点修改,区间查询,上面三个操作正好够用。

    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #include<iostream>
    #include<cstdlib>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    const int M = 1000005;
    typedef long long ll;
    
    int tree[M*4],k = 1,c,t,n,p,q,num;
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    void build(int l,int r,int x)//递归建树,到叶节点输入,之后递归返回修改 
    {
        if(l == r)    
        {
            tree[x] = read();
            return;
        }
        int mid = (l+r) >> 1;
        build(l,mid,x << 1);
        build(mid+1,r,x << 1 | 1);
        tree[x] = tree[x << 1] + tree[x << 1 | 1];
    } 
    void update(int k,int val,int l,int r,int x)//单点修改 
    {
        if(l == r)
        {
            tree[x] += val;
            return;
        }
        int mid = (l+r) >> 1;
        if(k <= mid) update(k,val,l,mid,x << 1);
        else update(k,val,mid+1,r,x << 1 | 1);
        tree[x] = tree[x << 1] + tree[x << 1 | 1];
    }//k为要修改的节点编号,val为修改值,l,r为左右区间
    int query(int kl,int kr,int l,int r,int x)
    {
        if(kl <= l && kr >= r) return tree[x];
        int mid = (l+r) >> 1;
        int sum = 0;
        if(kl <= mid) sum += query(kl,kr,l,mid,x << 1);
        if(kr > mid) sum += query(kl,kr,mid+1,r,x << 1 | 1);
        return sum;    
    } //kl为查询左端点,kr为查询右端点,l,r为当前区间作用端点,x为节点编号 
    int main()
    {
        t = read();
        while(t--)
        {
            n = read();
            build(1,n,1);
            c = read();
            rep(i,1,c)
            {
                num = read();
                if(num == 4) break;
                p = read(),q = read();
                if(num == 1) update(p,q,1,n,1);        
                if(num == 2) update(p,-q,1,n,1);
                if(num == 3) printf("%d
    ",query(p,q,1,n,1));
            }
        }
        return 0;
    }
    /*
    1
    10
    1 2 3 4 5 6 7 8 9 10
    7
    3 1 3
    1 3 6
    3 2 7
    2 10 2
    1 6 3
    3 3 10
    4
    */
  • 相关阅读:
    ....
    CodeForces 375A(同余)
    POJ 2377 Bad Cowtractors (最小生成树)
    POJ 1258 AgriNet (最小生成树)
    HDU 1016 Prime Ring Problem(全排列)
    HDU 4460 Friend Chains(bfs)
    POJ 2236 Wireless Network(并查集)
    POJ 2100 Graveyard Design(尺取)
    POJ 2110 Mountain Walking(二分/bfs)
    CodeForces 1059B Forgery(模拟)
  • 原文地址:https://www.cnblogs.com/captain1/p/8996763.html
Copyright © 2011-2022 走看看