树状数组++

版权声明:这是gigo写的QAQ https://blog.csdn.net/qq_42835815/article/details/85013424

区间修改,区间查询

差分还是很重要的思想。

我们设d_i=a_i-a_{i-1}

这个很正常就是差分对吧

然后很显然a_n=\sum_{i=1}^nd_i

但我们的目标是\sum_{i=1}^na_i

所以\sum_{i=1}^na_i=\sum_{i=1}^n\sum_{j=1}^id_j=\sum_{i=1}^n(n-i+1)d_i

所以我们把两层循环简化成了一层循环

但我们的目标是没有循环。

所以再进行简化

\sum_{i=1}^n(n-i+1)d_i=\sum_{i=1}^n(n+1)d_i-\sum_{i=1}^ni*d{i}

明显这个时候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的工作有两类:

  1. 图书馆经常购置新书,而书架任意时刻都是满的,所以只得将某位置的书拿掉并换成新购的书。
  2. 小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

猜你喜欢

转载自blog.csdn.net/qq_42835815/article/details/85013424