树状数组学习记录

文章介绍了树状数组的特点,如何使用change和query函数进行动态求前缀和以及区间操作,展示了在P3374、P3368和P1908等问题中的应用,包括逆序对计算和CF1042D中的子段和计数问题.
摘要由CSDN通过智能技术生成

        树状数组的特点:可以动态地求前缀和或任何可合并操作的问题,当插入一个数字时,只需要logn的时间复杂度就可以更新所有的前缀和。

1.树状数组构建的函数:

        change函数用于更新树状数组,每在x位置插入一个数字,就把所有大于x的前缀更新,这个函数也用于初始化树状数组。

        query函数用于查询从1到x的区间和,通过二进制进行。


int s[510000];
int n;
int lowbit(int x){
	return x&-x;
}

void change(int x,int k)
{
	while(x<=n) s[x]+=k,x+=lowbit(x);
}

int query(int x){//查询区间和 
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}	
2.应用:

P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

洛谷p3374:点修,区查,简单的树状数组函数操作可以完成。前缀和查询区间。 

注意:树状数组s必须从1开始储存

#include <iostream>
#define int long long
using namespace std;
int s[510000];
int n;
int lowbit(int x){
	return x&-x;
}

void change(int x,int k)
{
	while(x<=n) s[x]+=k,x+=lowbit(x);
}

int query(int x){//查询区间和 
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}	 
signed main(){
	int x,m;cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>x;change(i,x);
		}
		for(int i=1;i<=m;i++){
			int t;cin>>t;
			int x,y;
			if(t==1){
				cin>>x>>y;
				change(x,y);
			}else if(t==2){
				cin>>x>>y;
				cout<<query(y)-query(x-1)<<endl;
			} 
		}
		return 0;
}

P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

洛谷p3368:区修,点查,当进行区间操作的时候需要构建差分数组。此时树状数组求的前缀和变为差分数组的前缀和,差分数组可以具体求出某个点的坐标。

这里修改区间的时候只需要在左侧l加上要修改的值和右侧+1的位置减去修改的值,就可以改掉整个区间。

#include <iostream>
#define int long long
using namespace std;
int a[510000];
int s[510000];
int n;
int lowbit(int x){
	return x&-x;
} 
void change(int x,int k)
{
	if(x==0) x=n+1;
	while(x<=n) s[x]+=k,x+=lowbit(x);
}

int query(int x){
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}	 
signed main(){
	int x,m;cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		for(int i=n;i>=1;i--){
			a[i]-=a[i-1];
		}
		for(int i=1;i<=n;i++){
			change(i,a[i]);
		}
		for(int i=1;i<=m;i++){
			int t;cin>>t;
			int x,y,z;c 
			if(t==1){
				cin>>x>>y>>z;
				change(x,z);
				change(y+1,-z);
			}else if(t==2){
				cin>>x;
				cout<<query(x)<<endl;
			} 
		}
		return 0;
}

P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 

洛谷P1908:离散化+树状数组,天才的思路,即通过树状数组动态地查询前缀和,用前缀和表示出小于该数字且位置大于该数字的数量。

具体的流程是这样:

创建一个结构体,存数字的位置pos和值val。先按val的大小降序排序,如果val相同,再按pos的大小降序排序(对相同数字的处理)。

排序之后,先放大的数字(这样每次查询前缀和区间的时候,前缀和的结果就是大于放入数字的个数),即change(a[i].pos,1)。存入的是位置,因为大小通过排序已经确定了,但是位置必须要在该数字之前才能满足逆序的条件。

每放入一个数字就要计算大于它且位置在它之前的个数,sum+=query(a[i].pos-1)。

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
struct node{
	int val,pos;
}a[510000];
int s[510000];
int n;
int lowbit(int x){
	return x&-x;
}
void change(int x,int k)
{
	while(x<=n) s[x]+=k,x+=lowbit(x);
}
bool cmp(node x,node y){
	if(x.val==y.val){
		return x.pos>y.pos;
	}else return x.val>y.val;
}
int query(int x){//查询区间和 
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}	 
signed main(){
	while(cin>>n&&n){
		for(int i=1;i<=n;i++){
			cin>>a[i].val;a[i].pos=i;
		}
		sort(a+1,a+n+1,cmp);
		int sum=0;
		for(int i=1;i<=n;i++){
			change(a[i].pos,1);
			sum+=query(a[i].pos-1);
		}
		cout<<sum<<endl;
	}
	
	return 0;
}

Problem - 1042D - Codeforces

CF1042D:题意就是给一个数组,寻找一些子段满足子段和小于给定数,给出这样的子段总数(一个数也算子段)。

这个题没用离散化因为要用准确的数字来比较,树状数组求逆序数这类思路重点在于排序和放的顺序,对前缀和进行排序是因为放入的顺序影响结果,因为树状数组的位置在排序后赋予了大小的意义,即树状数组前面的数小,后面的数大。在放入当前数字时,便于query数组得到之前所有 a[i]<=a[j]-k这样不满足条件的数组,在他前面的一定不满足,算一遍前缀和即可。

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
int s[510000];
int a[510000];
int cnt[510000];
int n,k;
int lowbit(int x){
	return x&-x;
}
//这是一个改的函数,默认原来数组全是0,所以改也是初始化的过程 
void change(int x,int k)//x是新数组对应位,k是原数组对应位的数字
{
	while(x<=n+1) s[x]+=k,x+=lowbit(x);
}

int query(int x){//查询区间和 
	int t=0;
	while(x) t+=s[x],x-=lowbit(x);
	return t;
}
signed main(){
		cin>>n>>k;
		for(int i=2;i<=n+1;i++){
			cin>>a[i];
			a[i]=a[i]+a[i-1];
		}
		int ans=0;
		sort(cnt+1,cnt+2+n);
		for(int i=1;i<=n+1;i++){//这个i代表的是j的滚动。 
			int pos=upper_bound(cnt+1,cnt+2+n,a[i]-k)-cnt;//找的是最小j。即到底有多少个pre[j]-t小于了a[i]; 
			ans+=i-1-query(pos-1);//由于i<j,所以所有可能的i只出现在j的下面。 
			pos=lower_bound(cnt+1,cnt+2+n,a[i])-cnt;
			change(pos,1);//存的是a[i]在cnt中的位置,由于cnt是递增的,在后面计算的时候,会通过树状数组动态记录已有的答案,减去重复的i。 
		}
		cout<<ans<<endl;
		return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值