树状数组总结

目录

P3368

P1908

P1637

P1531


还记得上次,我用"P3374 【模板】树状数组 1 题解"作为题目,而这次,我将用"树状数组总结"作为题目。不废话了,本文就是一个总结。

首先,我将对这几个题目进行总结:这个&这个

第一个这个:

第二个这个:

(排名不分先后)

那么首先,就是最典型的一个例题:P3374

因为上次讲过了,所以就不讲了,具体就看这个

这次,先讲第二题:P3368

P3368

假设你看不见上面这行字(也看不见这行字)(看到的就点个赞)

那么,首先,题目就不发了,总结一下题意:

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

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

  2. 求出某一个数的值。

因此,我们可以先写出第一个操作:

cpp 复制代码
if(op==1){
    int x,y;
	long long k;
	cin>>x>>y>>k;
	add(x,k);
    add(y+1,-k);
}

当然,很符合题意:

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

然后,我们再写第二个操作:(上一个操作貌似包含容斥原理的成分🤭)

cpp 复制代码
else{
	int x;
	cin>>x;
	cout<<query(x)<<"\n";
}

当然,很符合题意:

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

其实输入也不能缺:

cpp 复制代码
for(int i=1;i<=n;i++){
	cin>>b;
	add(i,b-a);
	a=b;
}

ok,就剩函数了:

cpp 复制代码
void add(int x,int y){for(;x<=n;x+=(x&-x))c[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=c[x];return sum;}

接下来,就没什么可讲的了。

全篇完(那是不可能的)

AC code:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long c[500005];
void add(int x,int y){for(;x<=n;x+=(x&-x))c[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=c[x];return sum;}
int main(){
	cin>>n>>m;
	long long a=0,b;
	for(int i=1;i<=n;i++){
		cin>>b;
		add(i,b-a);
		a=b;
	}
	while(m--){
		int op;
		cin>>op;
		if(op==1){
			int x,y;
			long long k;
			cin>>x>>y>>k;
			add(x,k);
			add(y+1,-k);
		}
		else{
			int x;
			cin>>x;
			cout<<query(x)<<"\n";
		}
	}
	return 0;
}

接下来,我们来看一下:P1908

P1908

题意:

因此,依旧add和query:

cpp 复制代码
void add(int x,int y){
	for(;x<=n;x+=(x&-x))c[x]+=y;
}
int query(int x){
	int sum=0;
	for(;x;x-=(x&-x))sum+=c[x];
	return sum;
}

输入:

cpp 复制代码
for(int i=1;i<=n;i++){
	cin>>a[i];
	b[i]=a[i];
}

离散化:

cpp 复制代码
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
	a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}

统计求答案:

cpp 复制代码
int ans=0;
for(int i=1;i<=n;i++){
	int tmp=0;
	tmp=query(a[i]);
	add(a[i],1);
	ans+=i-1-tmp;
}
cout<<ans;

然后,也没什么可讲的。

过:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,a[500005],b[500005],c[500005];
void add(int x,int y){
	for(;x<=n;x+=(x&-x))c[x]+=y;
}
int query(int x){
	int sum=0;
	for(;x;x-=(x&-x))sum+=c[x];
	return sum;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(b+1,b+n+1,a[i])-b;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		int tmp=0;
		tmp=query(a[i]);
		add(a[i],1);
		ans+=i-1-tmp;
	}
	cout<<ans;
	return 0;
}

P1637

P1637:

首先:函数:

cpp 复制代码
void add(int x,int y){for(;x<100005;x+=(x&-x))d[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=d[x];return sum;}

主函数:

cpp 复制代码
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++){b[i]=query(a[i]-1);add(a[i],1);}// 计算 b [i]:a [i] 左边比它小的数字数量
memset(d,0,sizeof(d));
for(int i=n;i>=1;i--){c[i]=(n-i)-query(a[i]);add(a[i],1);}//计算 c [i]:a [i] 右边比它大的数字数量
long long ans=0;
for(int i=1;i<=n;i++)ans+=1LL*b[i]*c[i];//(累加答案)
cout<<ans;

然后,没了。

复制代码
#include<bits/stdc++.h>
using namespace std;
int a[30005],b[30005],c[30005],d[100005];
int n;
void add(int x,int y){for(;x<100005;x+=(x&-x))d[x]+=y;}
int query(int x){int sum=0;for(;x;x-=(x&-x))sum+=d[x];return sum;}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	memset(d,0,sizeof(d));
	for(int i=1;i<=n;i++){b[i]=query(a[i]-1);add(a[i],1);}
	memset(d,0,sizeof(d));
	for(int i=n;i>=1;i--){c[i]=(n-i)-query(a[i]);add(a[i],1);}
	long long ans=0;
	for(int i=1;i<=n;i++)ans+=1LL*b[i]*c[i];
	cout<<ans;
	return 0;
}

P1531

P1531:

build:

复制代码
void build(int o,int l,int r){
	if(l==r){
		tree[o]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(o*2,l,mid);
	build(o*2+1,mid+1,r);
	tree[o]=max(tree[o*2],tree[o*2+1]);
}

功能

线段树建树,维护区间最大值。

逻辑

  1. l==r 叶子节点:存原数组 a[l]
  2. 分左右子树递归建;
  3. 当前节点值 = 左、右子树最大值。

update:

复制代码
void update(int o,int l,int r,int p,int k){
	if(l==r){
		tree[o]=max(tree[o],k);
		return;
	}
	int mid=(l+r)/2;
	if(p<=mid)update(o*2,l,mid,p,k);
	else update(o*2+1,mid+1,r,p,k);
	tree[o]=max(tree[o*2],tree[o*2+1]);
}

作用

线段树单点更新,只有新值 k 比原成绩大时才修改(匹配题目 U 操作规则)

逻辑

  1. l==r 到达叶子节点,节点值取自身和 k 的较大值;
  2. 算 mid,判断要修改的位置 p 在左 / 右子树,递归更新;
  3. 回溯后用左右子树最大值更新当前节点。

query:

复制代码
int query(int o,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		return tree[o];
	}
	int mid=(l+r)/2;
	int ans=0;
	if(x<=mid)ans=max(ans,query(o*2,l,mid,x,y));
	if(y>mid)ans=max(ans,query(o*2+1,mid+1,r,x,y));
	return ans;
}

作用

线段树区间查询,求 \(x,y\) 区间最大值。

流程

  1. 当前区间 l,r 完全在查询范围 x,y 内,直接返回该节点最大值;
  2. 取中点 mid,分别递归查左、右子树;
  3. 把左右查到的结果取最大返回。

主函数:

复制代码
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	build(1,1,n);
	char op;
	int x,y;
	while(m--){
		cin>>op>>x>>y;
		if(op=='U'){
			update(1,1,n,x,y);
		}else{
			cout<<query(1,1,n,x,y)<<'\n';
		}
	}
	return 0;
}
  • 读入学生数 n、操作数 m;
  • 读入数组 a 1\~n,调用线段树建树 build;
  • 循环 m 次处理操作:
    • U x y:单点更新 x 位置,只变大不缩小;
    • Q x y:查询区间 x,y 最大值并输出;

然后,真的无了。

复制代码
#include<bits/stdc++.h>
using namespace std;
int a[200005];
int tree[20005*10*4];
void build(int o,int l,int r){
	if(l==r){
		tree[o]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(o*2,l,mid);
	build(o*2+1,mid+1,r);
	tree[o]=max(tree[o*2],tree[o*2+1]);
}
void update(int o,int l,int r,int p,int k){
	if(l==r){
		tree[o]=max(tree[o],k);
		return;
	}
	int mid=(l+r)/2;
	if(p<=mid)update(o*2,l,mid,p,k);
	else update(o*2+1,mid+1,r,p,k);
	tree[o]=max(tree[o*2],tree[o*2+1]);
}
int query(int o,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		return tree[o];
	}
	int mid=(l+r)/2;
	int ans=0;
	if(x<=mid)ans=max(ans,query(o*2,l,mid,x,y));
	if(y>mid)ans=max(ans,query(o*2+1,mid+1,r,x,y));
	return ans;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		cin>>a[i];
	}
	build(1,1,n);
	char op;
	int x,y;
	while(m--){
		cin>>op>>x>>y;
		if(op=='U'){
			update(1,1,n,x,y);
		}else{
			cout<<query(1,1,n,x,y)<<'\n';
		}
	}
	return 0;
}

这个博客,讲解了

  • P3368
  • P1908
  • P1637
  • P1531

最后,为了凑字数: