简单数据结构

一、树状数组


树状数组是一种支持单点修改区间查询的数据结构,这个数组是以二进制的形式存储的,例如7的二进制是111,最右边1代表1,那么c[7]就是从a[7]往前1个数这个区间所有a数组值的和,再例如8的二进制是1000,最右边的1代表8,那么c[8]就代表从a[8]往前8个数(1-8)这个区间所有a数组值的和。

树状数组有两个操作,修改和查询。

修改是往上修改,也就是例如修改a[1],则先修改c[1],再向上修改c[2],然后c[4],直到n为止。

而查询则是向下查询,例如查询前6个数的和,则ans先+=c[6],再+=c[4]。

而无论是修改的从1到2,再到4,再到8,还是查询的从6到4,都需要一个lowbit操作也就是x=x&(~x).

这样就可以在树状数组上进行操作了。

二、线段树

线段树是一个二叉树,每个节点存储的是一段区间的信息,根节点自然就是1-n总区间的信息,

而每个节点的两个子节点则是l到(r-l)/2和(r-l)/2+1到r这两段区间的信息。

线段树有四种操作:区间修改,区间查询,单点修改,单点查询,

单点修改和单点查询直接用类似二分查找的方法来做就行了,

区间查询:

例如一个1-8的线段树,如果要查询3-7,那么先判断3和7分别在左子树还是右子树,如果都有,那么分别修改。

先进入左子树,发现3-4这个节点被3-7完全包含那么直接取这个节点的信息,再看右子树,发现5-6这个节点被完全包含,也直接取这个节点的信息,再找7-8这个节点,发现未完全包含,则再往下查询,又发现7这个节点被完全包含,也直接取这个点的信息。

区间修改:

区间修改是最难的一个操作,他需要用到一个懒惰标记。还用上一个的例子,修改3-7的所有数的值都加1,发现3-4被完全包含,那么将这个节点打上一个值为1的懒惰标记,同样5-6和7两个节点也打上一个值为1的懒惰标记,而当再查询的时候,例如查询的3的值,发现走到3-4这个节点时有一个懒惰标记,那么就将这个标记下传到两个子节点。

总结一下就是,修改时当发现整个区间都包含时就打上一个标记,当查询到有标记的节点上时,就把标记下传。

下面给一个区间修改区间查询的模板

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstring>
#include<iostream>
#define ls l,m,root<<1
#define rs m+1,r,root<<1|1
#define LL long long
using namespace std;
LL sum[8000001];
LL a1[8000001];
void add(int root)
{
    sum[root]=sum[root<<1]+sum[root<<1|1];
}
void build(int l,int r,int root)
{
    a1[root]=0;
    if(l==r) 
    {
        scanf("%lld",&sum[root]);
        return;                 
    }
    int m=(l+r)>>1;
    build(ls);
    build(rs);
    add(root);
    return;
}
void sign(int root,int len)
{
    if(a1[root])
    {
        a1[root<<1]+=a1[root];
        a1[root<<1|1]+=a1[root];
        sum[root<<1]+=a1[root]*(len-(len>>1));
        sum[root<<1|1]+=a1[root]*(len>>1);
        a1[root]=0;
    }
}
void change(int L,int R,int c,int l,int r,int root){
    if(L<=l&&r<=R)
    {
        a1[root]+=c;
        sum[root]+=(LL)c*(r-l+1);
        return;                 
    }
    sign(root,r-l+1);
    int m=(l+r)>>1;
    if(L<=m) 
    {
        change(L,R,c,ls);
    }
    if(m+1<=R) 
    {
        change(L,R,c,rs);  
    }
    add(root);
}
LL find(int L,int R,int l,int r,int root){
    if(L<=l&&r<=R)
    {
        return sum[root];
    } 
    sign(root,r-l+1);
    LL tot=0;
    int m=(l+r)>>1;
    if(L<=m) 
    {
        tot+=find(L,R,ls);
    }
    if(m+1<=R)
    {
        tot+=find(L,R,rs);
    } 
    return tot;
}
int n,m;
int a,b;
char q[4];
int main()
{
    scanf("%d%d",&n,&m);
    build(1,n,1);
    LL c;
    for(int i=1;i<=m;i++)
    {
        scanf("%s",&q[1]);
        if(q[1]=='C')
        {
            scanf("%d%d%lld",&a,&b,&c);
            change(a+1,b+1,c,1,n,1);
            continue;
        }
        if(q[1]=='Q')
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",find(a+1,b+1,1,n,1));
            continue;
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值