树状数组和线段树

1.树状数组

 

例题1

        程序设计

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std ;

const int N = 100010;

int n , m ;                 //数的个数 n ; 操作次数 m

int a[N],tr[N];             //原数组 a[N] , 树状数组 tr[N] 

int lowbit(int x)           //返回x的最后一位1
{
    return x&-x;
}

void add(int x, int v)      //在x位置加v ,并且相关联的数组也加v
{
   for(int i=x; i<=n ; i += lowbit(i)) tr[i] += v ; 
}

int query(int x)            //查询x的前缀和
{
    int res = 0 ;
    for (int i=x ; i ; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);  //读取原数组
    for(int i=1;i<=n;i++) add(i,a[i]);        //搭建树状数组
    
    while(m--)
    {
        int k,x,y;
        scanf("%d%d%d",&k,&x,&y);
        if(k==0) printf("%d\n",query(y)-query(x-1));  //x到y所有数的和
        else add(x,y);
    }
    
    return 0;
}

 例题2

题目分析 

        题目要求某一个点(x,y)左下方星星的个数(不包括自己),且星星按y坐标增序给出,y 坐标相同的按x坐标增序给出, 因此对于每个新来的点(x,y), y是当前纵坐标的最大值,只需要求[x]中星星出现的数量即可。即为前缀和。

 程序设计

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace  std ;

const int N = 32010 ;

int n ;                  //n个星星

int tr[N] , level[N];    //树:tr[N]  等级:level[N]

int lowbit(int x)
{
    return x&-x ;
}

void add(int x)
{
    for(int i=x ; i < N ; i += lowbit(i)) tr[i] ++ ;
}

int query(int x)
{
    int res = 0;
    for(int i=x ; i ; i -= lowbit(i))  res += tr[i] ;
    return res ;
}

int main()
{
    int x , y ;
    scanf("%d",&n);
    for(int i=1 ; i<=n ; i++)  
    {
        scanf("%d%d",&x,&y) ;
        x++ ;                    //树状数组从1开始 , 但坐标从0开始,让横坐标x++
        level[query(x)] ++ ;     //查询x的前缀和,更新等级为level[query(x)] 的加 1
        add(x);                  //搭建树状数组
    }
    
    for(int i=0 ; i<n ; i++) printf("%d\n",level[i]) ;
    
    return 0;
}

2.线段树

 

 

例1

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=100010;

int n,m;   //数的个数n 操作次数m。

int w[N];  //记录一下权重

struct node{
    int l,r;    //左右区间
    int sum;    //总和
}tr[N*4];       //记得开 4 倍空间

void push_up(int u)     //利用它的两个儿子来算一下它的当前节点信息
{
    tr[u].sum = tr[u<<1].sum + tr[u<<1|1].sum; //左儿子 u<<1 ,右儿子 u<<1|1  
}

/**
 * build :搭建线段树
 * u :当前节点编号 
 * l :左边界
 * r :右边界
 * 无返回值
 */
void build(int u,int l,int r) 
{
    //如果当前已经是叶节点了,那我们就直接赋值就可以了
    if(l==r) tr[u]={l,r,w[r]}; 
    
    //否则的话,说明当前区间长度至少是 2 ,那么我们需要把当前区间分为左右两个区间,那先要找边界点
    else
    {
        tr[u]={l,r};//这里记得赋值一下左右边界的初值

        int mid=l+r>>1;//边界的话直接去计算一下 l + r 的下取整

        build(u<<1,l,mid);//先递归一下左儿子

        build(u<<1|1,mid+1,r);//然后递归一下右儿子

        push_up(u);//做完两个儿子之后的话呢 push_up更新一下当前节点信息
    }
}

/**
 * query :查询
 * u :查询节点编号 
 * l :左边界
 * r :右边界
 * return sum
 */
int query(int u,int l,int r)   //查询的过程是从根结点开始往下找对应的一个区间
{
    //如果当前区间已经完全被包含了,那么我们直接返回它的值就可以了
    if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
    
    //否则的话我们需要去递归来算
    int mid=tr[u].l+tr[u].r>>1;  //计算一下当前区间的中点是多少
    
    int sum=0;//用 sum 来表示一下我们的总和
    
    //判断中点和左右两边有没有交集
    if(mid>=l)   sum+=query(u<<1,l,r);      //看一下我们当前区间的中点和左边有没有交集
    if(r>=mid+1) sum+=query(u<<1|1,l,r);    //看一下我们当前区间的中点和右边有没有交集
    
    return sum;
}

/**
 * modify :修改
 * u :当前节点的编号 
 * l :要修改的位置
 * r :要修改的值
 * 无返回值
 */
void modify(int u,int x,int v)
{
    //如果当前已经是叶节点了,那我们就直接让他的总和加上 v 就可以了
    if(tr[u].l==tr[u].r)tr[u].sum+=v; 

    //否则的话我们需要去递归来算
    else
    {
      int mid=tr[u].l+tr[u].r>>1;   //计算一下当前区间的中点是多少
      
      //判断 x 是在左半边还是在右半边
      if(x<=mid) modify(u<<1,x,v);  //如果是在左半边,那就修改左儿子
      else modify(u<<1|1,x,v);      //如果在右半边,那就修改右儿子

      push_up(u);   //更新完之后刷新当前节点的信息
    }

}

int main()
{
    scanf("%d%d",&n,&m);

    for(int i=1;i<=n;i++)scanf("%d",&w[i]);

    build(1,1,n);   /*第一个参数是根节点的下标,根节点是一号点,然后初始区间是 1 到 n */

    //后面的话就是一些修改操作了

    while(m--)
    {
        int k,a,b;

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

        if(!k) printf("%d\n",query(1,a,b));//求和的时候,也是传三个参数,第一个的话是根节点的编号 ,第二个的话是我们查询的区间 
        //第一个参数也就是当前节点的编号
        else
        modify(1,a,b);//第一个参数是根节点的下标,第二个参数是要修改的位置,第三个参数是要修改的值

    }

    return 0;
}

例2

与上一道题类似,只要把求和过程改成求最大值即可。题目只要求查询,不需要再写修改函数

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include <bits/stdc++.h>

using namespace std;

const int N=100010;

int n,m;   //数的个数n 操作次数m。

int w[N];  //记录一下权重

struct node{
    int l,r ;   //该节点左右区间
    int maxv ;  //该节点的最大值
}tr[N*4];

/**
 * build :搭建线段树
 * u :当前节点编号 
 * l :左边界
 * r :右边界
 * 无返回值
 */
void build(int u, int l, int r)
{
    if (l == r)tr[u] = { l,r,w[r] };  
    else
    {
        tr[u] = { l,r };
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
    }
}

int query(int u,int l,int r)
{
    if (tr[u].l >= l && tr[u].r <= r)return tr[u].maxv;
    int mid = tr[u].l + tr[u].r >> 1;
    int maxv = INT_MIN;
    if (l <= mid)maxv = max(maxv, query(u << 1, l, r));
    if (r >= mid + 1)maxv = max(maxv, query(u << 1 | 1, l, r));
    return maxv;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)cin >> w[i];
    
    build(1, 1, n);
    
    int l, r;
    while (m--)
    {
        scanf("%d%d",&l,&r);
        printf("%d\n",query(1,l,r));
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值