树状数组与差分

目录

  • 树状数组的引入
  • lowbit的含义
  • 树状数组的前缀和存储方式
  • 单点修改
  • 区间查询
  • 初始化
  • 模板例题——树状数组基本操作
  • 差分——区间修改
  • 备注

@(树状数组算法详解·目录)

树状数组的引入

相信读者一定知道什么是前缀和,形如一串数\(a1,a2...,an,sum[i]=a[1]+a[2]+...+a[i]\)

前缀和在算法的优化上占有很重要的地位,一般就会预先对数据进行预处理运算以后,再在运算过程中用\(O(1)\)时间调用,这样的操作很大程度上避免了实际运算中的枚举,NOIP2016魔法阵就是一个典型的例题。

But前缀和也有其缺陷:

当原序列中的数据修改后如果快速调用前缀和??

在此,我们引入了一个名叫树状数组的算法,快速将单点修改和区间查询优化到\(O(logn)\)级别。


lowbit的含义

\(lowbit(i)\)表示非负整数i再二进制下最低位1以及末尾0的个数。

例如,二进制\(1001001100\)中,\(100\)的长度为\(3\),所以这个数字\(lowbit\)运算的结果为\(3\)。

根据计算机(dev-c++)的运算法则可得,lowbit(i)=i&-i .


树状数组的前缀和存储方式

对于每一个求和数组\(c[i],c[i]\)的求和范围\(a[i-lowbit(i)+1]\)~\(a[i]\).

用一幅图可以形象直观的解释其存储方式:


单点修改

对于\(add(x,val)\),表示\(a[x]\)加上了数值\(val\)。如何修改:

例如\(x=3\),需要修改的是\(3,4,8\)(若\(n=8\))

可见对于每一个修改的位置\(x\),下一个要修改的是\(x+lowbit(x).\)

即,\(3+lowbit(3)=4,4+lowbit(4)=8.\)

故得到代码如下:

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

区间查询

\(ask(x)\)表示要查询\(1\)~\(x\)的前缀和。

例如\(x=7,sum=c[7]+c[6]+c[4],\)

可见对于每一\(x\),下一步要累加的是\(c[x-lowbit(x)].\)

因此得到代码如下:

inline int ask(int x)
{
    int ans=0;
    for (int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
    return ans;
}

对于查询l到r区间的和,则\(sum=ask( r )-ask( l-1 ).\)


初始化

每一个\(c\)数组的求和范围是\(i-lowbit(i)+1\)~\(i,\)

可以利用最普通的前缀和直接求出\(1-i\)的和,

则\(c[i]=sum[i]-sum[i-lowbit(i)]\)。


模板例题——树状数组基本操作

如题,已知一个数列,你需要进行下面两种操作:

1.将某一个数加上x

2.求出某区间每一个数的和

操作1: 格式:1 x k 含义:将第x个数加上k

操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和

输出包含若干行整数,即为所有操作2的结果。

注意n≤500000.

code:

#include<bits/stdc++.h>
using namespace std;
const int maxn=500000;
int n,m;
int a[maxn+10];
int c[maxn+10];
int sum[maxn+10];
#define lowbit(x) (x&-x)
void add(int x,int val)
{
    for (int i=x;i<=n;i+=lowbit(i))
        c[i]+=val;
    return;
}
inline int ask(int x)
{
    int ans=0;
    for (int i=x;i>=1;i-=lowbit(i))
        ans+=c[i];
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i)
    {
        scanf("%d",a+i);
        sum[i]=sum[i-1]+a[i];
    }
    for (int i=1;i<=n;++i) c[i]=sum[i]-sum[i-lowbit(i)];
    for (int i=1;i<=m;++i)
    {
        int num,x,y;
        scanf("%d%d%d",&num,&x,&y);
        if (num==1) add(x,y);
        else printf("%d\n",ask(y)-ask(x-1));
    }
    return 0;
} 

差分——区间修改

已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数数加上x

2.求出某一个数的值

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x 含义:输出第x个数的值

注意n≤500000.

对于某一区间\(x~y\)加上\(k\),可以用一个数组b标记:\(b[x]\)加上\(k\),\(b[y+1]\)减去\(k\)。

仔细思考一下,其实十分巧妙:

对这个标记数组做前缀和,做到\(x\)及以后刚刚加上了k,但是做到\(y+1\)以后又减回去了。

因此我们只需要用树状数组去维护这个数组b的前缀和,对于操作\(2\)返回\(a[x]+ask(x)\)即可。

code:


#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&-x)
#define LL long long
const LL maxn=500000;
LL n,m;
LL a[maxn+10];
LL c[maxn+10];
void add(LL x,LL val)
{
    for (LL i=x;i<=n;i+=lowbit(i))
        c[i]+=val;
    return;
}
inline LL ask(LL x)
{
    LL sum=0;
    for (LL i=x;i>=1;i-=lowbit(i))
        sum+=c[i];
    return sum;
}
int main(void)
{
    scanf("%lld%lld",&n,&m);
    for (LL i=1;i<=n;++i)   scanf("%lld",a+i);
    for (LL i=1;i<=m;++i)
    {
        LL t;
        scanf("%lld",&t);
        if (t==1)
        {
            LL x,y,k;
            scanf("%lld%lld%lld",&x,&y,&k);
            add(x,k),add(y+1,-k);
        }
        if (t==2)
        {
            LL x;
            scanf("%lld",&x);
            printf("%lld\n",a[x]+ask(x));
        }
    }
    return 0;
}

备注

两道例题来自于洛谷\(P3374\)和\(P3368\).

欢迎指正!

原文地址:https://www.cnblogs.com/pigzhouyb/p/10119601.html

时间: 2024-08-29 21:43:57

树状数组与差分的相关文章

POJ3468 A Simple Problem with Interger [树状数组,差分]

题目传送门 A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 130735   Accepted: 40585 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One

Luogu3527 POI2011 Meteors 整体二分、树状数组、差分

传送门 比较板子的整体二分题目,时限有点紧注意常数 整体二分的过程中将时间在\([l,mid]\)之间的流星使用树状数组+差分进行维护,然后对所有国家查看一遍并分好类,递归下去,记得消除答案在\([mid+1,r]\)的询问中时间在\([l,mid]\)的流星操作的贡献 注意:可能存在某一段时间某一个国家的流星数量超过long long范围,应该当某个时候国家流星量和大于等于国家需求值时直接退出,这样可以避免这个问题. #include<bits/stdc++.h> #define INF 0

P3368【模板】树状数组 2 - 差分

建立两个差分数组,套公式就好了 c[i]表示i元素的"增量",下面的式子左边是序列从1 ~ x的前缀和整体增加的值 \[\sum_{i=1}^x\sum_{j=1}^ic[j] = (x+1)\sum_{i=1}^xc[i] - \sum_{i=1}^xi*c[i] \] #include <algorithm> #include <iostream> #include <cstring> #include <cstdio> using

回顾树状数组

树状数组是一种常用的数据结构,能够在O(log2n)的时间内进行单点修改和求前缀和.因为代码量小.常熟小往往在某些应用中快于线段树(当然有些问题是不能呢用树状数组完成的). 最基本的树状数组 方法1:用一个数组,O(1)修改, O(n)查询 方法2:求前缀和,O(n)修改,O(1)查询 以上两种方法卡一卡就TLE了. 树状数组就平衡了一下这两种方法,修改的时候多修改一点,查询的时候多算一点. 为了清楚地明白树状数组是如何运作的,我们实现定义 lowbit(x) 是写成2进制后的x中最后一位1.例

HDU 4325 离散化+树状数组 或者 不使用树状数组

题意:给出一些花的开放时间段,然后询问某个时间点有几朵花正在开放. 由于ti<1e9,我们需要先将时间离散化,然后将时间点抽象为一个数组中的点,显然,我们需要进行区间更新和单点查询,可以考虑线段树与树状数组两种做法,一般的,树状数组是用来维护区间和与单点修改的,那么,如何通过树状数组进行区间更新和单点查询呢,联想到差分数组,差分数组可以在o(1)的时间内进行区间的更新,但是单点查询的效率为o(n),显然不能用于处理此题,这时,考虑树状数组维护差分数组,就可以o(logn)地进行区间更新(更新差分

浅析树状数组

目录 beginning 顺序结构 A+B 高精 A+B 压位高精 A+B 二分A+B 树状数组简介(不喜欢啰嗦的请直接跳到这里) 基础概念 代码实现 大体结构 lowbit lowbit的作用 总结+代码 逆序对 离散化 方式 实现 代码 树状数组进阶 差分 区间修改+单点查询 主要思想 单点查询 区间修改 区间修改+区间查询 差分分析 代码 2D树状数组 query update 区间修改+单点查询 区间修改+区间查询 时间复杂度 罗列例题 一维 beginning 顺序结构 A+B 高精

csp-s模拟测试56(10.2)Merchant「二分」&#183;Equation「树状数组」

又死了......T1 Merchant 因为每个集合都可以写成一次函数的形式,所以假设是单调升的函数,那么随着t越大就越佳 而单调减的函数,随着t的增大结果越小,所以不是单调的??? 但是我们的单调只需凭借t时刻的sum值是否大于S即可 如果某个单减的集合符合情况,那么他在t==0时就符合情况 如果不符合,那么他就不会作出贡献 所以可以二分 T2 Equation 一开始以为是高斯消元??? 当然不是..... 把每个xi均用x1表示,那么我们发现,对于深度奇偶不同的点,他的表示方式是不同的,

树状数组的原理和基础应用

这样的数据结构称作树状数组,它支持O(logN)的单点修改和区间查询,效率高并且代码简洁,缺点在于适用范围不如线段树广.不难看出(雾),tree[i]表示a[i]及之前的 lowbit(i)个 数,定义lowbit(i)等于取i的二进制中最后一个'1'表示的大小观察发现(.),修改a[i]只需更新包含i的节点,倒推可知从i开始,每次把i加上lowbit(i),这些节点包含了a[i]从i开始,每次把i减去lowbit(i)直到为0,这些节点的值加起来就是前缀和.例题1:树状数组1  https:/

差分数列+树状数组

差分数列+树状数组:可以把树状数组的“单点修改,区间查询”-->改变为“区间修改和单点查询” 例题: codevs 1081 线段树练习 2 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 给你N个数,有两种操作 1:给区间[a,b]的所有数都增加X 2:询问第i个数是什么? 输入描述 Input Description 第一行一个正整数n,接下来n行n个整数,再接下来一个正整数Q,表示操作的个数. 接下来Q行每行若干个整数