DAY_2(树状数组)

本文介绍了树状数组的基本概念,包括前缀和的计算方法,以及如何进行单点更新、区间查询、区间修改等操作,涉及低位运算和二进制转换,重点展示了两种模版:单点更新+区间查询和区间更新+单点查询的应用实例。
摘要由CSDN通过智能技术生成

今天是第二天了,加油加油

首先引入预备知识--前缀和

前缀和

给一个N个数所在数组A,另设一个新数组B,令B[i]=A[0]+A[1]+A[2]+...+A[i-1]+A[i](B[0]=A[0])

不过为了方便,A,B数组有时下标从1开始

主要用途::区间求和

构造树状数组

如图,有以下规律:

C[1]=A[1]

C[2]=A[1]+A[2]

C[3]=A[3]

C[4]=A[1]+A[2]+A[3]+A[4]

......

若将数组的下标换成二进制

1=(001)  C[1]=A[1]

2=(010)  C[2]=A[1]+A[2]

3=(011)  C[3]=A[3]

4=(100)  C[4]=A[1]+A[2]+A[3]+A[4]

......

所以能得到结论:项数=二进制最右边(最低位)的1代表的整数

求lowbit

f1:

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

f2:(常用)

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

实现前缀和

设一个数组sum,表示前缀和 如: sum[7]=C[4]+C[6]+C[7]

将下标转换成二进制:

sum[111]=C[100]+C[110]+C[111]

int sum(int i)
{
	int ret=0;
	while(i>0)
	{
		ret+=c[i];
		i-=lowbit(i);
	}
	return ret;
}

 时间复杂度:O(logn)

区间查询: sum[R]-sum[L-1]

单点更新+区间查询

单点更新

将原始数组更新成树状数组,存入c数组中

void add(int p,int val)//单点更新 
{
	while(p<=n)
	{
		c[p]+=val;
		p+=lowbit(p);//叶子节点向上更新数组 
	}//更新是查询的逆过程 
}
 区间查询

求区间[ l,r ]内a [i]之和

int ask(int x)
{
    int sum=0;
    while(x!=0)
    {
        sum+=c[x];
        x-=lowbit(x);
    }
}
int ask_range(int l,int r)
{
    return ask(r)-ask(l-1);
}

区间更新+单点查询

利用差分使问题转换成普通的树状数组

设有原数组a [i],另设d [i]

d[i]=a[i]-a[i-1](a[0]=0)

则可通过d [i]前缀和实现单点查询a [i]

后续若对【r,l】修改值x,只需 d[l]+=x,d[r+1]-=x

区间修改
void add(int p,int x)//单点更新差分 
{
	while(p<=n)
	{
		c[p]+=x;
		p+=lowbit(p);
	}
}
void add_range(int l,int r,int x)//区间修改
{
	add(l,x);
	add(r+1,-x);
}
 单点查询
int ask(int p)//因为差分,故单点值即为前缀值 
{
	int sum=0;
	while(p)
	{
		sum+=c[p];
		p-=lowbit(p);
	}
	return sum;
}

区间修改+区间查询

位置p的前缀和: \sum_{i=1}^{p}a[i]=\sum_{i=1}^{p}\sum_{j=1}^{i}d[i]

经推导,可知:d [1]用了p次,d [2]用了p-1次......

\sum_{i=1}^{p}a[i]=\sum_{i=1}^{p}d[i]*(p-i+1)=(p+1)*\sum_{i=1}^{p}d[i]-\sum_{i=1}^{p}d[i]*i

维护两个数组:

c1 [i]=d [i]

c2 [i]=d [i] * i

区间修改
void add(int p,int x)//差分数组的树状数组 
{
	ll k=p;
	while(p<=n)
	{
		c1[p]+=x;
		c2[p]+=x*k;
		p+=lowbit(p);
	}
}
void add_range(int l,int r,int k)
{
	add(l,k);
	add(r+1,-k);
}
查询前缀和&&区间查询
int ask(int x)//前缀和查询,查询[1,x]的和 
{
	int sum=0,k=x;
	while(x!=0)
	{
		sum+=(k+1)*c1[x]-c2[x];
		x-=lowbit(x);
	}
	return sum;
}
int ask_range(int l,int r)//区间查询 
{
	return ask(r)-ask(l-1);
}

下面是今天写的题目::

 P3374 【模板】树状数组 1

本题用的是单点更新+区间查询的模版

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m;
int a[N];//原数组
int d[N];//树状数组 
int com,x,y,k;
int lowbit(int x)
{
	return x&-x;
}
void add(int p,int x)
{
	while(p<=n)
	{
		d[p]+=x;
		p+=lowbit(p);
	}
}
int ask(int x)
{
	int sum=0;
	while(x!=0)
	{
		sum+=d[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		add(i,a[i]);
	}
	for(int i=1;i<=m;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>k;
			add(x,k);
		}
		if(com==2)
		{
			cin>>x>>y;
			cout<<ask(y)-ask(x-1)<<endl;
		}
	}
}

P3368 【模板】树状数组 2

本题用的模版是区间更新+单点查询的模版

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define ll long long
ll n,m;
ll a[N];//原数组
ll d[N];//树状数组
ll c[N];//差分数组 
ll com,x,y,k;
ll lowbit(ll x)
{
	return x&-x;
}
void add(ll p,ll x)
{
	while(p<=n)
	{
		d[p]+=x;
		p+=lowbit(p);
	}
}
ll ask(ll x)
{
	ll sum=0;
	while(x!=0)
	{
		sum+=d[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		c[i]=a[i]-a[i-1];
		add(i,c[i]);
	}
	for(int i=1;i<=m;i++)
	{
		cin>>com;
		if(com==1)
		{
			cin>>x>>y>>k;
			add(x,k);
			add(y+1,-k);
		}
		if(com==2)
		{
			cin>>x;
			cout<<ask(x)<<endl;
		}
	}
	return 0;
}

P2068 统计和

和树状数组模版1差不多,代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n;
ll w;
char com;
ll d[N];
ll x,y;
ll lowbit(ll x)
{
	return x&-x;
}
void add(ll p,ll x)
{
	while(p<=n)
	{
		d[p]+=x;
		p+=lowbit(p);
	}
}
ll ask(ll x)
{
	ll sum=0;
	while(x!=0)
	{
		sum+=d[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	cin>>n>>w;
	for(int i=1;i<=w;i++)
	{
		cin>>com>>x>>y;
		if(com=='x')
		add(x,y);
		if(com=='y')
		cout<<ask(y)-ask(x-1)<<endl;
	}
	return 0;
}

P2357 守墓人

这题用的是区间更新+区间查询的模版,代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
#define ll long long
ll n;
ll a[N];//原数组 
ll d2[N];//二阶查分数组 
ll d[N];//差分数组的树状数组 
ll c[N];//差分数组 
ll c1[N],c2[N];//维护两个树状数组 
ll com;
ll l,r,k;
ll f; 
ll cnt;
ll bo;
ll lowbit(ll x)
{
	return x&-x;
}
void add(ll p,ll x)//差分数组的树状数组 
{
	ll k=p;
	while(p<=n)
	{
		c1[p]+=x;
		c2[p]+=x*k;
		p+=lowbit(p);
	}
}
void add_range(ll l,ll r,ll k)
{
	add(l,k);
	add(r+1,-k);
}
ll ask(ll x)
{
	ll sum=0,k=x;
	while(x!=0)
	{
		sum+=(k+1)*c1[x]-c2[x];
		x-=lowbit(x);
	}
	return sum;
}
ll ask_range(ll r,ll l)
{
	return ask(r)-ask(l-1);
}
int main()
{
	cin>>n>>f;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		c[i]=a[i]-a[i-1];
		add(i,c[i]);
	}
	for(int i=1;i=f;i++)
	{
		cnt++;
		cin>>com;
		if(com==1)
		{
			cin>>l>>r>>k;
			add_range(l,r,k);
		}
		else if(com==2)
		{
			cin>>k;
			add_range(1,1,k);
		}
		else if(com==3)
		{
			cin>>k;
			add_range(1,1,-k);
		}
		else if(com==4)
		{
			cin>>l>>r;
			cout<<ask_range(r,l)<<endl;
		}
		else if(com==5)
		cout<<ask_range(1,1)<<endl;
		if(cnt==f)
		break;
	}
	return 0;
}

P1438 无聊的数列

这题难度明显上升,用到了二阶差分数组,然后又用上区间更新+单点查询,二阶差分数组真的很难评,推导还是很麻烦的:

第一种情况:首项为 K,公差为 0(整体增加 K)

那么显然,可以得到,在这种情况下:

d[l]​=d[l]​+k

d[r]+1​=d[r]+1​−k

再继续深入分析,可以得到

d2[l]​=d2[l]​+k

d2[l]+1​=d2[l]+1​−k

d2[r]+1​=d2[r]+1​−k

d2[r]+2​=d2[r]+2​+k

第二种情况:首项为 0,公差为 D

一通操作得到

d[l]+1​=d[l]+1​+D,d[l]+2​=d[l]+2​+D,...,d[l]+r​=d[l]+r​+D

d[r]+1​=d[r]+1​−(r−l)×D

继续一通操作易得:

d2[l]+1​=d2[l]+1​+D

d2[r]+1​=(r−l+1)×D

d2[r]+2​=d2[r]+2​+(r−l)×D

将这些东西加到一起,可以得到,对于整体增加一个首项为 K,公差为 D的等差数列

d2[l​]=d2[l]​+k

d2[l]+1​=d2[l]+1​+D−K

d2[r]+1​=d2[r]+1​−(r−l+1)×D+K

d2[r]+2​=d2[r]+2​+(r−l)×D+K

---摘自洛谷神犇ll_dio题解

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n,m;//数列长度和操作次数 
ll l,r,k,o;
ll a[N];//原始数组
ll d[N];//差分数组 
ll c[N];//二阶差分数组 
ll m1[N],m2[N];//维护的两个树状数组 
ll opt; 
ll p;
ll lowbit(ll x)
{
	return x&-x;
}
void add(ll p,ll x)
{
	ll k=p;
	while(p<=n)
	{
		m1[p]+=x;
		m2[p]+=x*k;
		p+=lowbit(p);
	}
}
ll ask(ll x)
{
	ll k=x,sum=0;
	while(x)
	{
		sum+=(k+1)*m1[x]-m2[x];
		x-=lowbit(x);
	}
	return sum;
}
int main()
{
	cin>>n>>m;
	for(ll i=1;i<=n;i++)
	{
		cin>>a[i];
		d[i]=a[i]-a[i-1];
		add(i,d[i]-d[i-1]);
	}
	for(ll i=1;i<=m;i++)
	{
		cin>>opt;
		if(opt==1)
		{
			cin>>l>>r>>k>>o;
			add(l,k);
			add(l+1,o-k);
			add(r+1,-k-(r-l+1)*o);
			add(r+2,(r-l)*o+k);
		}
		if(opt==2)
		{
			cin>>p;
			cout<<ask(p)<<endl;
		}
	}
	return 0;
}

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值