今天是第二天了,加油加油
首先引入预备知识--前缀和
前缀和
给一个N个数所在数组A,另设一个新数组B,令
不过为了方便,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,表示前缀和 如:
将下标转换成二进制:
int sum(int i)
{
int ret=0;
while(i>0)
{
ret+=c[i];
i-=lowbit(i);
}
return ret;
}
时间复杂度:O(logn)
区间查询:
单点更新+区间查询
单点更新
将原始数组更新成树状数组,存入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]
后续若对【r,l】修改值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的前缀和:
经推导,可知:d [1]用了p次,d [2]用了p-1次......
故
维护两个数组:
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;
}