树状数组,线段树学习笔记

树状数组

解决的问题:区间求和;
在这里插入图片描述

主要模板

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

void add(int x,int c)//修改单点值
{
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
}

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

还可以当做差分数组思想用:
题目
代码

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
int  a[N];
ll t[N];
int n,m;

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

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

ll sum(int x)
{
    ll res=0;
    for(int i=x;i;i-=lowbit(i)) res+=t[i];
    return res;
    
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
     cin>>a[i];
     for(int i=1;i<=n;i++)
      add(i,a[i]-a[i-1]);
      char op[2];
      int l,r,d;
     while(m--)
     {
         cin>>op>>l;
         if(op[0]=='C')
         {
             cin>>r>>d;
             add(l,d),add(r+1,-d);
         }
         else 
         {
             cout<<sum(l)<<endl;
         }
     }
      
    
    return 0;
}

线段树

线段树可以是一把大砍刀什么都能运用到,基本上大部分的区间操作
比如说:单点修改,单点查询,区间修改,区间查询,区间开方,区间四则运算.
线段树主要形式还是一课树的形式,树上的节点表示的区间范围;


线段树tr[u]当中的u我们可以看成父节点,所以他的左儿子的序号节点就为2u即为(u>>1),右儿子的序号节点就为2u+1即为(u>>1|1)这样我们就能在tr数组中快速找到左右儿子的节点,而且我们还能运用线段树中的懒标记来进一步优化效率

懒标记:当我们的查询区间已经包含在我们的修改区间当中时即(tr[u].l>=l&&tr[u].r<=r) 我们就不需要在进行对子节点的修改,直接对该节点的懒标记加上当前要修改的值,tr[u].add+=x;(懒标记的值可以叠加)。

pushdown(对懒标记的一个运算):从上往下,用父节点的懒标记来更新子节点。

pushup :从下往上,用子节点来更新父节点,一般是子节点被修改的时需要重新更新父节点的值。

我们可以以一个题目为例子。题目入口

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
1、C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案

要求 :区间修改,区间查询。
做法:线段树,当我们在进行区间修改时可以运用到懒标记。

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
typedef long long ll;
struct node
{
    int l,r;
    ll sum,add;//sum为总和,add为懒标记的值;
}tr[N*4];//线段树的定义

int w[N];
int n,m;

int pushup(int u)
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;//更新父节点的sum值。
}

void pushdown(int u)//懒标记
{
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)//该根节点还有懒标记
    {
        left.add+=root.add;//左子树加上根节点的懒标记;
        left.sum+=(ll)(left.r -left.l+1)*root.add;//左子树加上修改的数。
        right.add+=root.add;//右子树加上根节点的懒标记;
        right.sum+=(ll)(right.r- right.l+1 )*root.add;//右子树加上修改的数
        root.add=0;//根节点的懒标记清空为0
    }
}

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

void modify(int u,int l,int r,ll d)
{
    if(tr[u].l>=l&&tr[u].r<=r)//如果当前区间已经被修改包含,则该区间的子节点不会自己查询,用懒标记表示在这个区间修改的数
    {
        tr[u].add+=d;//懒标记可以叠加,加上这个数;
        tr[u].sum+=(tr[u].r-tr[u].l+1)*d;//总和加上这段区间里的增加的数目;
    }
    else
    {
        pushdown(u);//修改当前区间要先查看是否还有懒标记,有懒标记要先清除,即加到子节点当中。
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,d);//修改区间横跨两个区间
        if(r>mid) modify(u<<1|1,l,r,d);
        pushup(u);//pushup一遍来更新父节点。
    }
}

ll query(int u,int l,int r)
{
    if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;//如果当前区间已经被修改包含,直接返回该区间的sum值
    else
    {
        pushdown(u);//修改当前区间要先查看是否还有懒标记,有懒标记要先清除,即加到子节点当中。
        int mid=tr[u].l+tr[u].r>>1;
        ll res=0;
        if(l<=mid) res=query(u<<1,l,r);//修改区间横跨两个区间
        if(r>mid) res+=query(u<<1|1,l,r);
        return res;
    }
}


int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    build(1,1,n);
    
    char op[2];
    int l,r,v;
    while(m--)
    {
        scanf("%s%d%d",op,&l,&r);
        if(op[0]=='Q')
        printf("%lld\n",query(1,l,r));
        else
        {
            scanf("%d",&v);
            modify(1,l,r,v);
        }
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值