高级数据结构—树状数组

引入问题:

给出一个长度为n的数组,完成以下两种操作:
1. 将第i个数加上k

2. 输出区间[i,j]内每个数的和

朴素算法:

单点修改:O( 1 )

区间查询:O( n )

使用树状数组:

单点修改:O( logn )

区间查询:O( logn )

前置知识:

lowbit()
运算:非负整数x在二进制表示下最低位1及其后面的0构成的数值。

如lowbit(12)= 1100 = 4

函数实现: 

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

树状数组思想:

树状数组的主要思想就是利用树结构维护“前缀和”。

对于一个序列,对其建立如下树形结构:

1.每个结点t[x]保存以x为根的子树中叶结点值的和
2.每个结点覆盖的长度为lowbit(x)
3.t[x]结点的父结点为t[x + lowbit(x)]
4.树的深度为log2n+1

树状数组操作:

add(x, k)表示将序列中第x个数加上k。

在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。

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

ask(x)表示将查询序列前x个数的和

以ask(7)为例:
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),例如 7 - lowbit(7) = 6

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

例题: 

楼兰图腾

题目链接:楼兰图腾

从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。

从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。

代码附上:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
int tr[N];
int low[N],great[N];
int a[N];
int n;

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;
	}
	return;
}

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

signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];

	for(int i=1;i<=n;i++){//记录i点左边的信息
		int y=a[i];
		low[i]=ask(y-1);//low[i]记录左边[1,y-1]区间的数字个数
		great[i]=ask(n)-ask(y);//great[i]记录左边[y+1,n]区间的数字个数
		add(y,1);//加入树状数组中,出现一次+1
	}

	memset(tr,0,sizeof(tr));//初始化
	int ans1=0,ans2=0;

	for(int i=n;i>=1;i--){//记录i点右边的信息
		int y=a[i];
		ans2+=low[i]*ask(y-1);//乘法原理
		ans1+=great[i]*(ask(n)-ask(y-1));
		add(y,1);
	}

	cout<<ans1<<" "<<ans2<<"\n";

	return 0;
}

一个简单的整数问题

题目链接:一个简单的整数问题

区间修改 + 求单点

那么这道题目可以采用差分的方法去做,在上一道题目中我们求的是区间和,采用前缀和的思想去做,那么这里我们可以把单点a[i]看成前缀和,因为a[i]=\sum差分和。

首先我们要先建差分数组:

for(int i=1;i<=n;i++){
        add(i,a[i]-a[i-1]);
    }

 那么接下来的情况就跟第一题一样了

代码附上:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N =1e5+5;
int a[N],tr[N],low[N],great[N];
int n,q;

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;
    }
    return;
}

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

signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++)cin>>a[i];

    for(int i=1;i<=n;i++){
        add(i,a[i]-a[i-1]);
    }

    while(q--){
        char op;cin>>op;
        if(op=='C'){
            int l,r,d;cin>>l>>r>>d;
            add(l,d);
            add(r+1,-d);
        }
        else if(op=='Q'){
            int l;cin>>l;
            cout<<ask(l)<<"\n";
        }
    }

    return 0;
}

一个简单的整数问题2

题目链接:一个简单的整数问题2

区间查询+区间修改

直接上图:

 所以这道题目我们需要维护两个树状数组,一个是d[i],另一个是i*d[i]。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5+5;
int n,m;
int a[N];
int tr[N],tr2[N];
//tr[]数组是原始数组的差分数组d[i]的树状数组
//tr2[]数组是原始数组的差分数组乘以i即i*d[i]的树状数组

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

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

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

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

int ask2(int x){
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i)){
        res+=tr2[i];
    }
    return res;
}

int get_sum(int x){//最后一步的推导
    return ask1(x)*(x+1)-ask2(x);
}

signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    memset(tr2,0,sizeof(tr2));
    memset(tr,0,sizeof(tr));
    for(int i=1;i<=n;i++)cin>>a[i];

    for(int i=1;i<=n;i++){
        int b=a[i]-a[i-1];
        add1(i,b);
        add2(i,b*i);
    }

    while(m--){
        char op;cin>>op;
        if(op=='Q'){
            int l,r;cin>>l>>r;
            cout<<get_sum(r)-get_sum(l-1)<<"\n";
        }
        else if(op=='C'){
            int l,r,d;cin>>l>>r>>d;
            add1(l,d),add1(r+1,-d);
            add2(l,l*d),add2(r+1,(r+1)*(-d));
        }
    }

    return 0;
}

谜一样的牛

题目链接:谜一样的牛

仅仅根据题目的信息,很难找出他们之间的高度关系。面对这种题目,我们可以从边界去思考,(正着不行就反着来),从最后一头牛看,最后一头牛的a[i]表示前i头牛有x个比它矮的,那么也就意味着它排第x+1位,那么从倒数第二头牛来看,前面有y头比它矮的,那最后一头牛可能比它高也可能比它矮,因此,对于倒数第二头牛而言,它应该在除去上述x+1的区间[1,n]中,选取A(n−1)+1小的数。其他的牛以此类推。

假如建立一个全部元素为1的数列,某个位置的数为1代表这个高度还不知道是哪头牛的,那么就用树状数组维护该数列的前缀和,若某个位置的前缀和等于A(i)+1此时的下标就是要找的数。选择这个数后,将相应位置的1置0.可以二分这个位置。

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5+5;
int a[N],ans[N],tr[N];
int n;

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;
	}	
	return;
}

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

signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
	a[1]=0;
	for(int i=2;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)tr[i]=lowbit(i);//初始化

	for(int i=n;i>=1;i--){//从后往前枚举
		int k=a[i]+1;//找剩余的牛中高度排第k
		int l=1,r=n;
		while(l<r){
			int mid=(l+r)/2;
			if(ask(mid)>=k)r=mid;
			else l=mid+1;
		}
		ans[i]=r;
		add(r,-1);//高度排第r的牛去掉
	}

	for(int i=1;i<=n;i++){
		cout<<ans[i]<<"\n";
	}

    return 0;
}

相关推荐

  1. 数据结构-树状数组

    2024-04-22 17:00:06       34 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-22 17:00:06       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-22 17:00:06       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-22 17:00:06       82 阅读
  4. Python语言-面向对象

    2024-04-22 17:00:06       91 阅读

热门阅读

  1. AI先驱者丹尼尔·丹尼特去世

    2024-04-22 17:00:06       31 阅读
  2. JDK中用到了哪些设计模式

    2024-04-22 17:00:06       183 阅读
  3. npm 常用命令详解

    2024-04-22 17:00:06       175 阅读
  4. flask 请求对象

    2024-04-22 17:00:06       122 阅读
  5. 分组带给了我们哪些?

    2024-04-22 17:00:06       166 阅读
  6. Elasticsearch:(二)3.安装Elasticsearch-head插件

    2024-04-22 17:00:06       72 阅读
  7. linux 查看nginx日志

    2024-04-22 17:00:06       39 阅读