SDNUOJ 1665-1668(树状数组的应用)

本文详细介绍了树形数组的概念、操作原理及其在区间查询和单点修改问题中的应用。通过实例代码展示了如何实现树形数组的add和ask操作,并提供了两种不同的构建方法。此外,还提及了树形数组在区间修改和区间查询问题中的解决方案,以及其与线段树的比较。

这篇文章写的很详细:
链接
这个b站上的视频讲的也很透彻:
链接
什么是树形数组:
在这里插入图片描述
树形数组是一种用来维护前缀和的工具,上图中序列上的就是树形数组,它虽然长得像个树,但是确是一个数组,比如上面的t[4]的值就是1-4的前缀和,其他元素以此类推。

add操作实现原理:
在这里插入图片描述
ask操作实现原理:
在这里插入图片描述
下面是区间查询+单点修改(1665and1666)的代码:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数 
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组 
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
	ll ans=0;
	for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层 
	{
		ans+=tree[j];	
	}
	return ans;
}//ask操作,求1-r的前缀和 
void add(ll p,ll w)
{
	for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作 
	{
		tree[j]+=w;
	}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树形数组上是让含有dat[p]的项都加w 
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		dat[i]=read();
	}
	for(int i=1;i<=n;++i)
	{
		ll k=lowbit(i);
		for(int j=1;j<=k;++j)
		{
			tree[i]+=dat[i-k+j];//树状数组储存 
		}
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		p_l=read();
		w_r=read();
		switch(op)
		{
			case 1:
				{
					write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
					printf("\n");
					break;
				}
			case 2:
				{
					dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值 
					add(p_l,w_r);
					break;
				}
		}
	}
	return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/

下面是另一种构建树状数组的方法(这种比较好,上面那种是我自己按照意思写的):

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);//操作树形数组的核心,求的是二进制下的a从最低位开始一直到前面一个非0的数组成的10进制数 
}
ll dat[100005],tree[100005];//dat是数据数组,tree是树形数组 
ll n,q,op,p_l,w_r;
ll ask(ll r)
{
	ll ans=0;
	for(int j=r;j>=1;j-=lowbit(j))//j-=lowbit(j)是去了左上一层 
	{
		ans+=tree[j];	
	}
	return ans;
}//ask操作,求1-r的前缀和 
void add(ll p,ll w)
{
	for(int j=p;j<=n;j+=lowbit(j))//用j+=lowbit(j)对每层进行操作 
	{
		tree[j]+=w;
	}
}//add操作,表现在dat数组上是让dat[p]+=w,表现在树状数组上是让含有dat[p]的项都加w 
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		dat[i]=read();
		add(i,dat[i]);//树状数组储存 
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		p_l=read();
		w_r=read();
		switch(op)
		{
			case 1:
				{
					write(ask(w_r)-ask(p_l)+dat[p_l]);//相减再补值得到区间和
					printf("\n");
					break;
				}
			case 2:
				{
					dat[p_l]+=w_r;//数据数组也要更改一下,求区间和时可能会拿来补值 
					add(p_l,w_r);
					break;
				}
		}
	}
	return 0;
}
/*
5 3
0 0 0 0 0
2 1 100
2 2 1000
1 2 5
*/

下面是区间修改+区间查询(1667and1668)的代码:
听说这种题用线段树比较好解= =,我用树状数组也就是套了个公式(蒟蒻瑟瑟发抖)。
在这里插入图片描述
其中tree1维护了差分数组b[i]的前缀和,tree2维护了i*b[i]的前缀和,sum是数据数组a的前缀和。代码中没有写出差分数组b是因为可以利用树状数组不用写出而直接求其前缀和。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[1000005],sum[1000005],tree1[1000005],tree2[1000005];
ll n,q,op,l,r,w;
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
inline void write(ll x)
{
	if (x < 0) x = ~x + 1, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
ll lowbit(ll a)
{
	return a&(-a);
}
ll ask1(ll r)
{
	ll ans=0;
	for(int i=r;i>=1;i-=lowbit(i))
	{
		ans+=tree1[i];	
	}
	return ans;
}
ll ask2(ll r)
{
	ll ans=0;
	for(int i=r;i>=1;i-=lowbit(i))
	{
		ans+=tree2[i];
	}
	return ans;
}
void add1(ll p,ll w)
{
	for(int i=p;i<=n;i+=lowbit(i))
	{
		tree1[i]+=w;
	}
}
void add2(ll p,ll w)
{
	for(int i=p;i<=n;i+=lowbit(i))
	{
		tree2[i]+=w;
	}
}
int main()
{
	n=read();
	q=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=read();
		if(i==1) sum[i]=a[i];
		else sum[i]=sum[i-1]+a[i];
	}
	for(int i=1;i<=q;++i)
	{
		op=read();
		switch(op)
		{
			case 1:
				{
					l=read();
					r=read();
					write((sum[r]+(r+1)*ask1(r)-ask2(r))-(sum[l-1]+l*ask1(l-1)-ask2(l-1)));
					printf("\n");
					break;
				}
			case 2:
				{
					l=read();
					r=read();
					w=read();
					add1(l,w);
					add1(r+1,-w);
					add2(l,l*w);
					add2(r+1,-(r+1)*w);
					break;
				}
		}
	}
	return 0;
} 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值