树状数组和线段树(蓝桥)

树状数组和线段树

数组数组:

1.用来做单点修改

2.动态求区间和

线段树:

1.树状数组能干的都能干

2.还可以求区间最大值

3.求区间长度

4.有很广的应用

1.动态求连续区间和
题意:

给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b]的连续和。接下来 m行,每行包含三个整数 k,a,b(k=0,表示求子数列[a,b][a,b]的和;k=1表示第 a 个数加 b)。

数列从 11 开始计数。

数据范围

1≤n≤100000
1≤m≤100000
1≤a≤b≤n

思路:

这道题有俩种操作,1.给数组中的某个数增加一个数 2.求区间和。 所以这是一道动态求区间和的题目。虽然用前缀和也能做,但是本题数据量大,会超时。所以本题用树状数组和线段树来做。

树状数组的时间复杂度为logn,本题的数据复杂读就是nlogn是可以的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C6tJRYZy-1584446431842)(C:\Users\15209\Pictures\博客\数组数组.jpg)]

代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N=100010;
int n,m;
int a[N],tr[N];

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

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

int query(int a)
{
    int sum=0;
    for(int i=a;i;i-=lowbit(i)) sum+=tr[i];

    return sum;
}

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==1)
        {
            add(x,y);
        }else
        {
            cout<<query(y)-query(x-1)<<endl;
        }

    }
    return 0;

}

本题还可以用线段树来做,线段树的原理图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j4e12cmu-1584446431844)(C:\Users\15209\Pictures\博客\线段树.jpg)]

代码:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=100010;
int w[N];
struct Node{
    int l,r;
    int sum;
}tr[N*4];

int n,m;

void pushup(int u)
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}

void build(int u,int l,int r)
{
    if(l==r) tr[u]={l,r,w[r]};
    else
    {
        tr[u]={l,r};
        int mid=tr[u].l+tr[u].r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}

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

    return sum;

}

void modify(int u,int x,int v)
{
    if(tr[u].l==tr[u].r) tr[u].sum+=v;
    else
    {
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid) modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}

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

    int l,r,k;
    while(m--)
    {
        scanf("%d%d%d",&k,&l,&r);
        if(k==0)
            printf("%d\n",query(1,l,r));
        else 
            modify(1,l,r);

    }

    return 0;
}

2.数星星
题意:

天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。

如果一个星星的左下方(包含正左和正下)有 k 颗星星,就说这颗星星是 k 级的。

给定 N个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。

输入:

第一行一个整数 N,表示星星的数目;

接下来 N 行给出每颗星星的坐标,坐标用两个整数 x,y表示;

不会有星星重叠。星星按y坐标增序给出y 坐标相同的按 xx 坐标增序给出。

思路:

本题虽然看上去是一个二维的题目,但是本题输入的数据有特点,它是按y轴从小到大输入的,并且按x轴也从小到大输入。因此当前点的高度一定是最高的,只需要要考虑x就可以了,计算x小于当前x的和就是该点的等级。因此这里用到了前缀和。可以用树状数组来做。

代码:
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N=32010;
int tr[N],level[N],sum[N];

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

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

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

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        x++;
        level[query(x)]++; //先计算起前面有多少个点,再加上当前点
        add(x);
    }
    
    for(int i=0;i<n;i++) cout<<level[i]<<endl;
    
    return 0;
}
3.数列区间最大值
题意:

输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y要求你说出 X到 Y 这段区间内的最大数。

思路:

这道题要求的是区间的最大值,可以用线段树来做,直接将线段树的模板代码中的求和sum,改成求max即可,pushup操作改成求最大值操作。

代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <climits>

using namespace std;
const int N=100010;

struct Node
{
    int l,r;
    int maxv;
}tr[N*4];
int n,m;
int w[N];

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);//原pushup操作
        
    }
}

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 = query(u << 1, l, r);
    if (r > mid) maxv = max(maxv, query(u << 1 | 1, l, r));
    return maxv;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值