区间修改,区间查询
差分还是很重要的思想。
我们设
这个很正常就是差分对吧
然后很显然
但我们的目标是
所以
所以我们把两层循环简化成了一层循环
但我们的目标是没有循环。
所以再进行简化
明显这个时候n+1已经是已知的,那我们只需要维护di和i*di两个树状数组就可以啦
上代码
#include<bits/stdc++.h>
#define in read()
using namespace std;
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();
if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}int n,m;
long long a[100003];
long long d[100003],y[100003];
int lowbit(int x){
return x&(-x);
}
void updated(int x,int k){
while(x<=n){
d[x]+=k;
x+=lowbit(x);
}
}
void updatey(int x,int k){
while(x<=n){
y[x]+=k;
x+=lowbit(x);
}
}
long long queryy(int x){
long long ans=0;
while(x>0){
ans+=y[x];
x-=lowbit(x);
}
return ans;
}
long long queryd(int x){
long long ans=0;
while(x>0){
ans+=d[x];
x-=lowbit(x);
}
return ans;
}
long long sum(int x){
return (x+1)*queryd(x)-queryy(x);
}
int main(){
n=in;m=in;
for(int i=1;i<=n;i++){
a[i]=in;
updated(i,a[i]);updated(i+1,-a[i]);
updatey(i,a[i]*i);updatey(i+1,-a[i]*(i+1));
}int p,x,y,k;
while(m--){
p=in;x=in;y=in;
if(p==1){
k=in;
updated(x,k);updated(y+1,-k);
updatey(x,k*x);updatey(y+1,-k*(y+1));
}
else{
printf("%lld\n",sum(y)-sum(x-1));
}
}
return 0;
}
到现在为止,我们已经学完了树状数组一维的所有知识啦
总体思想就是前缀和+差分
并且树状数组的代码,难度,和常数都比线段树小,但是树状数组能做的事线段树都能做,反过来却不一定。
不过因为我不会线段树,所以
树状数组最棒啦
顺带一提我们给出一道例题。WOJ1717
题面:
小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架。虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的。
具体说来,书架由N个书位组成,编号从1到N。每个书位放着一本书,每本书有一个特定的编码。 小J的工作有两类:
- 图书馆经常购置新书,而书架任意时刻都是满的,所以只得将某位置的书拿掉并换成新购的书。
- 小J需要回答顾客的查询,顾客会询问某一段连续的书位中某一特定编码的书有多少本。
例如,共5个书位,开始时书位上的书编码为1,2,3,4,5
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:1
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:1
此时,图书馆购进一本编码为“1”的书,并将它放到2号书位。
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:0
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:2 ……
你的任务是写一个程序来回答每个顾客的询问。
输入
第一行两个整数N,M,表示一共N个书位,M个操作。
接下来一行共N个整数数A1,A2…AN,Ai表示开始时位置i上的书的编码。
接下来M行,每行表示一次操作,每行开头一个字符
若字符为‘C’,表示图书馆购进新书,后接两个整数A(1<=A<=N),P,表示这本书被放在位置A上,以及这本书的编码为P。
若字符为‘Q’,表示一个顾客的查询,后接三个整数A,B
,K(1<=A<=B<=N),表示查询从第A书位到第B书位(包含A和B)中编码为K的书共多少本。
输出
对每一个顾客的查询,输出一个整数,表示顾客所要查询的结果。
样例输入
5 5
1 2 3 4 5
Q 1 3 2
Q 1 3 1
C 2 1
Q 1 3 2
Q 1 3 1样例输出
1
1
0
2提示
对于40%的数据,1<=N,M<=5000 对于100%的数据,1<=N,M<=100000 对于100%的数据,所有出现的书的编码为不大于2147483647的正数。
如果你真的在线一步步做,那就上天台了ovo
因为你需要开十万棵线段树。
所以我们考虑离线做。
另外你还可以注意一件事,对于一本书来说,别的书增加减少除非是把这本书去掉,否则不会对这本书产生什么影响。
所以你可以先把操作拆分。
查询就是查询,换书可以看成原书删除和新书插入。
所以离线之后我们只需要按照书号排序,对每本书依次处理,最后按照查询的顺序输出就可以了。
我的代码比较菜,很多无用的东西没有用上,反正内存不会爆ovo
上代码
#include<bits/stdc++.h>
#define endl '\n'
#define in read()
using namespace std;
inline int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();
if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}
//n,m如题所示,mean记录操作类型,who记录操作影响书,l,r用于记录查询时的左右端点
//pos记录插入书或者删除书的位置,id记录本次操作的序号。
//tot就是全局变量
int n,m;
int a[300003];//mean为1:查询,2:删除 3:插入
struct node{
int who,mean,l,r,pos,id;
}t[300003];
int tot;
inline bool cmp(const node &a,const node &b){
if(a.who!=b.who)return a.who<b.who;
return a.id<b.id;
}
int d[300003],ans[300003];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int k){
while(x<=n){
d[x]+=k;
x+=lowbit(x);
}
}
inline int query(int x){
int ans=0;
while(x>0){
ans+=d[x];
x-=lowbit(x);
}
return ans;
}
int tot2=0;
int main(){
n=in;m=in;
for(register int i=1;i<=n;i++){
a[i]=in;
++tot;
t[tot].who=a[i];t[tot].mean=3;t[tot].pos=i;t[tot].id=tot;
}
char c;int x,y,z;
while(m--){
c=getchar();
while (c!='Q'&&c!='C') c=getchar();
x=in;y=in;
if(c=='Q'){
z=in;tot++;t[tot].id=tot;t[tot].l=x;t[tot].r=y;t[tot].who=z;t[tot].mean=1;t[tot].pos=++tot2;
}
else{
++tot;
t[tot].who=a[x];t[tot].mean=2;t[tot].pos=x;t[tot].id=tot;
++tot;
t[tot].who=y;t[tot].mean=3;t[tot].pos=x;t[tot].id=tot;
a[x]=y;
}
}
sort(t+1,t+tot+1,cmp);
// for(register int i=1;i<=tot;i++){
// cout<<t[i].who<<" "<<t[i].mean<<" "<<t[i].l<<" "<<t[i].r<<" "<<t[i].pos<<" "<<t[i].id<<endl;
// }
for(register int i=1;i<=tot;i++){
if(t[i].who!=t[i-1].who){
memset(d,0,sizeof(d));
}
if(t[i].mean==1){
ans[t[i].pos]=query(t[i].r)-query(t[i].l-1);
}
if(t[i].mean==2){
update(t[i].pos,-1);
}
if(t[i].mean==3){
update(t[i].pos,1);
}
}
for(register int i=1;i<=tot2;i++){
cout<<ans[i]<<endl;
}
return 0;
}
然后我简要说说二维
二维的树状数组也很好理解,用一个二维数组,然后代码变成了这样:
int lowbit(int x){
return x&(-x);
}
void update(int a,int b,int k){
int bb=b;
while(a<=n){
while(b<=m){
d[a][b]+=k;
b+=lowbit(b);
}
b=bb;//注意每次b循环完后要重置为原始b,否则就爆炸了
a+=lowbit(a);
}
}
long long query(int x,int y){
int yy=y;long long ans=0;
while(x>0){
while(y>0){
ans+=d[x][y];
y-=lowbit(y);
}
x-=lowbit(x);
y=yy;
}
return ans;
}
只需要两层循环就可以啦ovo。
二维的也可以支持区间修改区间查询哦!
我懒得解释了,,直接上代码,各位明白一下差分的感觉就行了。
维护了四个数组
WOJ4100
#include<bits/stdc++.h>
#define endl '\n'
#define in read()
using namespace std;
inline int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();
if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}
int n,m;
int a[300003];//mean为1:查询,2:删除 3:插入
struct node{
int who,mean,l,r,pos,id;
}t[300003];
int tot;
inline bool cmp(const node &a,const node &b){
if(a.who!=b.who)return a.who<b.who;
return a.id<b.id;
}
int d[300003],ans[300003];
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int k){
while(x<=n){
d[x]+=k;
x+=lowbit(x);
}
}
inline int query(int x){
int ans=0;
while(x>0){
ans+=d[x];
x-=lowbit(x);
}
return ans;
}
int tot2=0;
int main(){
n=in;m=in;
for(register int i=1;i<=n;i++){
a[i]=in;
++tot;
t[tot].who=a[i];t[tot].mean=3;t[tot].pos=i;t[tot].id=tot;
}
char c;int x,y,z;
while(m--){
c=getchar();
while (c!='Q'&&c!='C') c=getchar();
x=in;y=in;
if(c=='Q'){
z=in;tot++;t[tot].id=tot;t[tot].l=x;t[tot].r=y;t[tot].who=z;t[tot].mean=1;t[tot].pos=++tot2;
}
else{
++tot;
t[tot].who=a[x];t[tot].mean=2;t[tot].pos=x;t[tot].id=tot;
++tot;
t[tot].who=y;t[tot].mean=3;t[tot].pos=x;t[tot].id=tot;
a[x]=y;
}
}
sort(t+1,t+tot+1,cmp);
// for(register int i=1;i<=tot;i++){
// cout<<t[i].who<<" "<<t[i].mean<<" "<<t[i].l<<" "<<t[i].r<<" "<<t[i].pos<<" "<<t[i].id<<endl;
// }
for(register int i=1;i<=tot;i++){
if(t[i].who!=t[i-1].who){
memset(d,0,sizeof(d));
}
if(t[i].mean==1){
ans[t[i].pos]=query(t[i].r)-query(t[i].l-1);
}
if(t[i].mean==2){
update(t[i].pos,-1);
}
if(t[i].mean==3){
update(t[i].pos,1);
}
}
for(register int i=1;i<=tot2;i++){
cout<<ans[i]<<endl;
}
return 0;
}
推荐例题:WOJ的3860,2138,3605