20201019 day39 复习10:数据结构之树状数组、线段树

1 树状数组1

problem

单点修改,区间求和。

solution

树状数组直接维护原数组。初始化的时候用changex函数直接进行修改。

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,p[500005],a[500005];
int read(){
	int a=0,op=1;
	char c;c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') op=-1;c=getchar();
	}
	while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
	return a*op;
}
int lowbit(int x){
	return x&(-x);	
}
void changex(int num,int x){
	for(int i=num;i<=n;i+=lowbit(i)) a[i]+=x;
	return ;
}
int findx(int x){
	int sum=0;
	for(int i=x;i;i-=lowbit(i)) sum+=a[i];	
	return sum;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) p[i]=read();
	for(int i=1;i<=n;i++) changex(i,p[i]);
	while(m--){
		int b=0,x=0,y=0;
		b=read(),x=read(),y=read();
		if(b==1) changex(x,y);
		if(b==2) printf("%d\n",findx(y)-findx(x-1));
	}
	return 0;
}

2 树状数组2

problem

区间修改,单点查询。

solution

树状数组处理差分数组,差分数组的前缀和就是某一位的数。修改的时候,修改\([x,y]\)其实就是第\(x\)位加,第\(y+1\)位减去。

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
int n,m,p[500005],a[500005];
int read(){
	int a=0,op=1;
	char c;c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') op=-1;c=getchar();
	}
	while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
	return a*op;
}
int lowbit(int x){
	return x&(-x);	
}
void changex(int num,int x){
	for(int i=num;i<=n;i+=lowbit(i)) a[i]+=x;
	return ;
}
int findx(int x){
	int sum=0;
	for(int i=x;i;i-=lowbit(i)) sum+=a[i];	
	return sum;
}
int chafen[500005];
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) p[i]=read();
	chafen[1]=p[1];
	for(int i=2;i<=n;i++) chafen[i]=p[i]-p[i-1];
	for(int i=1;i<=n;i++) changex(i,chafen[i]);
	while(m--){
		int b=0,x=0,y=0,z=0;
		b=read();
		if(b==1) 
			x=read(),y=read(),z=read(),changex(x,z),changex(y+1,-z);
		if(b==2) x=read(),printf("%d\n",findx(x));
	}
	return 0;
}

3 二维树状数组1

loj133

problem

二维矩阵中,修改\(A_{x,y}\)的值,或者查询\((a,b)\)\((c,d)\)之间的矩阵元素和。
【单点修改,区间查询】

solution

与一维类似。使用二维前缀和。

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
long long c[5005][5005];
int n,m;
long long read(){
	long long a=0,op=1;
	char c;c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') op=-1;c=getchar();
	}
	while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
	return a*op;
}
int lowbit(int x){
	return x&(-x);	
}
void changex(int x,int y,long long num){
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			c[i][j]+=num;
}
long long findx(int x,int y){
	long long ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ans+=c[i][j];
	return ans;
}
void subtask1(){
	int x,y,k;
	x=read(),y=read(),k=read();
	changex(x,y,k);	
}
void subtask2(){
	int x1,y1,x2,y2;
	x1=read(),y1=read(),x2=read(),y2=read();
	long long ans=findx(x2,y2)+findx(x1-1,y1-1)-findx(x1-1,y2)-findx(x2,y1-1);
	printf("%lld\n",ans);	
}
signed main(){
	n=read(),m=read();
	//printf("%d %d\n",n,m);
	int op;
	while(~scanf("%d",&op)){
		if(op==1) subtask1();
		else subtask2();
	}
	return 0;
}

4 二维树状数组2

loj134

problem

区间修改,单点查询。

solution

二维差分,灵魂在这里:

void insert(int a,int b,int c,int d,int k){
	changex(a,b,k),changex(a,d+1,-k);
	changex(c+1,b,-k),changex(c+1,d+1,k);	
} 

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
long long c[5005][5005];
int n,m;
long long read(){
	long long a=0,op=1;
	char c;c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') op=-1;c=getchar();
	}
	while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
	return a*op;
}
int lowbit(int x){
	return x&(-x);	
}
void changex(int x,int y,long long num){
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			c[i][j]+=num;
}
long long findx(int x,int y){
	long long ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			ans+=c[i][j];
	return ans;
}
void insert(int a,int b,int c,int d,int k){
	changex(a,b,k),changex(a,d+1,-k);
	changex(c+1,b,-k),changex(c+1,d+1,k);	
} 
void subtask1(){
	int a,b,c,d,k;
	a=read(),b=read(),c=read(),d=read(),k=read();
	insert(a,b,c,d,k);
}
void subtask2(){
	int x,y;
	x=read(),y=read();
	printf("%lld\n",findx(x,y));	
}
int main(){
	n=read(),m=read();
	//printf("%d %d\n",n,m);
	int op;
	while(~scanf("%d",&op)){
		if(op==1) subtask1();
		else subtask2();
	}
	return 0;
}

5 树状数组3

problem

区间修改,区间查询。

solution

考察:

\[\begin{align} \sum_{i=1}^na_i &= a_1+a_2+\cdots +a_n \\ &=d_1+(d_1+d_2)+(d_1+d_2+d_3)+\cdots \\ &=nd_1+(n-1)d_2+...+d_n \\ &=n\sum_{i=1}^nd_i -[0\times d_1+1\times d_2+\cdots +(n-1)\times d_n)]\\ \end{align}\]

维护差分数组\(d_i\)和数组\((i-1)d_i\),分别进行前缀和操作。区间修改借助差分数组即可(因为两个数组都满足差分的性质)

code

#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
long long read(){
	long long a=0,op=1;
	char c;c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') op=-1;c=getchar();
	}
	while(c>='0'&&c<='9') a*=10,a+=c^48,c=getchar();
	return a*op;
}
int lowbit(int x){
	return x&(-x);	
}
const int maxn=1e7+10;
int n,k;
long long a[maxn],sum[maxn],c1[maxn],c2[maxn];
void addc1(int p,long long w){
	while(p<=n) c1[p]+=w,p+=lowbit(p);
	return ;	
}
void addc2(int p,long long w){
	while(p<=n) c2[p]+=w,p+=lowbit(p);
	return ;	
}
long long askc1(int p){
	long long ans=0;
	while(p) ans+=c1[p],p-=lowbit(p);
	return ans;	
}
long long askc2(int p){
	long long ans=0;
	while(p) ans+=c2[p],p-=lowbit(p);
	return ans;	
}
void subtask1(){
	int l,r;long long x;
	l=read(),r=read(),x=read();
	addc1(l,x),addc1(r+1,-x),addc2(l,l*x),addc2(r+1,-(r+1)*x);
}
void subtask2(){
	int l,r;l=read(),r=read();
	long long ans;
	ans=sum[r]+(r+1)*askc1(r)-askc2(r)-(sum[l-1]+l*askc1(l-1)-askc2(l-1));
	printf("%lld\n",ans);	
}
int main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
	while(k--){
		int a;a=read();	
		if(a==1) subtask1();
		else subtask2();
	}
	return 0;
}

6 线段树1[区间gcd]

problem

给定序列,求指定区间内所有数的gcd。

solution

把线段树求和的求和回溯改成gcd。没有太大的区别。很好的板子应用。

thoughts

10pts:p<<1写成了p<<2
100pts:正解。

code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int read(){
	int a=0,op=1;char c=getchar();
	while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;
}
const int maxn=2e5+10;
int a[maxn<<2],tree[maxn<<2],n,m;
int gcd(int x,int y){
	return y==0?x:gcd(y,x%y);
}
void buildtree(int p,int l,int r){
	if(l==r){tree[p]=a[l];return ;}
	int mid=l+r>>1;
	buildtree(p<<1,l,mid);buildtree(p<<1|1,mid+1,r);
	tree[p]=gcd(tree[p<<1],tree[p<<1|1]);
	return ;
}
int asktree(int p,int l,int r,int askl,int askr){
	if(l==askl&&r==askr) return tree[p];
	int mid=l+r>>1;
	if(askl>mid) return asktree(p<<1|1,mid+1,r,askl,askr);
	if(askr<=mid) return asktree(p<<1,l,mid,askl,askr);
	else return gcd(asktree(p<<1|1,mid+1,r,mid+1,askr),asktree(p<<1,l,mid,askl,mid));
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	buildtree(1,1,n);
	int v,b;
	while(m--){
		v=read(),b=read();
		printf("%d\n",asktree(1,1,n,v,b));
	}
	return 0;
}

7 区间\(\sin\)

problem

序列支持区间修改,所有的值加上k,区间查询\(\sum\limits_{i=l}^r\sin(a_i)\)

solution

维护是基于

\[\sin(x+y)=\sin x\cos y+\sin y\cos x \]

\[\cos(x+y)=\cos x\cos y-\sin x\sin y \]

然后就非常好做了,我们同时维护区间 \(\sin\)和跟区间\(\cos\)和,上传信息可以直接相加,再维护一个加法标记,用上面的和角公式可以做到 \(\Theta(1)\)下传标记。
复杂度\(\Theta(m\log n)\)

thoughts

0pts:主函数里的\(\operatorname{query}(l,n,1,n,1,k)\)\(l\)写成了\(1\)

code

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int read(){
	int a=0,op=1;char c=getchar();
	while(c>'9'||c<'0') {if(c=='-') op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;
}
#define mid (l+r>>1)
int n,q;
const int maxn=2e5+5;
int a[maxn];
double sink,cosk;
struct Segment_Tree{
	double sine[maxn<<2],cosi[maxn<<2];
	long long tag[maxn<<2];
	inline void pushup(int u){
		sine[u]=sine[u<<1]+sine[u<<1|1];
		cosi[u]=cosi[u<<1]+cosi[u<<1|1];
	}
	inline void update(int u,double sinx,double cosx){
		double sina=sine[u],cosa=cosi[u];
		sine[u]=sina*cosx+cosa*sinx;
		cosi[u]=cosa*cosx-sina*sinx;	
	}
	inline void pushdown(int u){
		if(!tag[u]) return ;
		double sinx=sin(tag[u]),cosx=cos(tag[u]);
		update(u<<1,sinx,cosx);update(u<<1|1,sinx,cosx);
		tag[u<<1]+=tag[u],tag[u<<1|1]+=tag[u];
		tag[u]=0;	
	}
	void build(int l,int r,int u){
		if(l==r){sine[u]=sin(a[l]),cosi[u]=cos(a[l]);return ;}
		build(l,mid,u<<1),build(mid+1,r,u<<1|1);
		pushup(u);	
	}
	void modify(int nl,int nr,int l,int r,int u,int k){
		if(nl<=l&&r<=nr){
			update(u,sink,cosk);tag[u]+=k;return ;	
		}
		pushdown(u);
		if(nl<=mid) modify(nl,nr,l,mid,u<<1,k);
		if(nr>mid) modify(nl,nr,mid+1,r,u<<1|1,k);
		pushup(u);
	}
	double query(int nl,int nr,int l,int r,int u){
		if(nl<=l&&r<=nr) return sine[u];
		double res=0;
		pushdown(u);
		if(nl<=mid)res+=query(nl,nr,l,mid,u<<1);
		if(nr>mid) res+=query(nl,nr,mid+1,r,u<<1|1);
		return res;	
	}
}T;
int main(){
	int op,l,r,k;
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	T.build(1,n,1);
	q=read();
	while(q--){
		op=read(),l=read(),r=read();
		if(op==1){
			k=read();sink=sin(k),cosk=cos(k);
			T.modify(l,r,1,n,1,k);
		}
		else printf("%.1lf\n",T.query(l,r,1,n,1));
	}
	return 0; 
} 

8 线段树

problem

区间修改,区间查询。

solution

lazy标记。真没啥可写的嘿嘿嘿。

code

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e6+5;
#define int long long
int tree[maxn<<2],a[maxn],lazy[maxn<<2];
int n,m;

int read(){
	int a=0,op=1;char c;c=getchar();
	while(c<'0'||c>'9'){if(c=='-')op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;	
}
void build(int p,int l,int r){
	if(l==r){tree[p]=a[l];return ;}
	int mid=l+r>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
void pushdown(int p,int l,int r){
	int mid=l+r>>1;
	tree[p<<1]+=(mid-l+1)*lazy[p];
	lazy[p<<1]+=lazy[p];
	tree[p<<1|1]+=(r-mid)*lazy[p];
	lazy[p<<1|1]+=lazy[p];
	lazy[p]=0;
}
void update(int p,int l,int r,int ll,int rr,int x){
	if(l==ll&&r==rr){tree[p]+=x*(r-l+1);lazy[p]+=x;return ;}
	pushdown(p,l,r);
	int mid=l+r>>1;
	if(ll>mid) update(p<<1|1,mid+1,r,ll,rr,x);
	else if(mid>=rr) update(p<<1,l,mid,ll,rr,x);
	else update(p<<1,l,mid,ll,mid,x),update(p<<1|1,mid+1,r,mid+1,rr,x);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
int query(int p,int l,int r,int askl,int askr){
	int mid=l+r>>1;
	if(l==askl&&r==askr) return tree[p];
	if(lazy[p]!=0) pushdown(p,l,r);	
	if(mid>=askr) return query(p<<1,l,mid,askl,askr);
	else if(mid<askl) return query(p<<1|1,mid+1,r,askl,askr);
	else return query(p<<1,l,mid,askl,mid)+query(p<<1|1,mid+1,r,mid+1,askr); 
}
int d,f,g,h;
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;i++){
		d=read();
		if(d==1) f=read(),g=read(),h=read(),update(1,1,n,f,g,h);
		else f=read(),g=read(),printf("%lld\n",query(1,1,n,f,g));
	}
	return 0;
}

9 线段树[AHOI2008]

problem

区间乘法,区间加法,区间求和并取模。

solution

两个lazy标记,一个加一个乘,乘法在加法之前,pushdown是关键。

code

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e6+5;
#define int long long
int tree[maxn<<2],a[maxn],lazy1[maxn],lazy2[maxn];
struct node{
	int add,mul;
}lazy[maxn<<2];
int n,m,mod;
//void pushup(int p){tree[p]=(tree[p<<1]%mod+tree[p<<1|1]%mod)%mod;}
void pushup(int x){
  (tree[x]=tree[x<<1]+tree[x<<1|1])%=mod;
}
int read(){
	int a=0,op=1;char c;c=getchar();
	while(c<'0'||c>'9'){if(c=='-')op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;	
}
void build(int p,int l,int r){
	lazy1[p]=0,lazy2[p]=1;
	if(l==r){tree[p]=a[l];return ;}
	int mid=l+r>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
	pushup(p);	
}
void tag(int p,int l,int r,int k1,int k2){
	lazy1[p]=(lazy1[p]*k2+k1)%mod;
	lazy2[p]=(lazy2[p]*k2)%mod;
	tree[p]=(tree[p]*k2+k1*(r-l+1))%mod;	
}
void pushdown(int p,int l,int r){
	int mid=l+r>>1;
	tag(p<<1,l,mid,lazy1[p],lazy2[p]);
	tag(p<<1|1,mid+1,r,lazy1[p],lazy2[p]);
	lazy1[p]=0,lazy2[p]=1;	
}
void modify1(int p,int l,int r,int nl,int nr,int k){
	if(nl<=l&&r<=nr) {tag(p,l,r,0,k);return ;}
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) modify1(p<<1,l,mid,nl,nr,k);
	if(nr>mid) modify1(p<<1|1,mid+1,r,nl,nr,k);
	pushup(p);
}
void modify2(int p,int l,int r,int nl,int nr,int k){
	if(nl<=l&&r<=nr) {tag(p,l,r,k,1);return ;}
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) modify2(p<<1,l,mid,nl,nr,k);
	if(nr>mid) modify2(p<<1|1,mid+1,r,nl,nr,k);
	pushup(p);	
}int x,y,k;
int query(int p,int l,int r,int nl,int nr){
	int res=0;
	if (nl<=l&&r<=nr) return tree[p];
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) res+=query(p<<1,l,mid,nl,nr);
	if(nr>mid) res+=query(p<<1|1,mid+1,r,nl,nr);
	return res%mod;	
}
void d(){
	printf("%lld %lld %lld:",x,y,k);
	for(int i=1;i<=n;i++) printf("%lld ",query(1,1,n,i,i));
	printf("\n");	
}
signed main(){
	n=read(),mod=read();
	//printf("%lld\n",mod);
	for(int i=1;i<=n;i++) a[i]=read();
	m=read();
	build(1,1,n);
	for(int i=1;i<=m;i++) {
		int op=read();
		if(op==1) x=read(),y=read(),k=read(),modify1(1,1,n,x,y,k);
		if(op==2) x=read(),y=read(),k=read(),modify2(1,1,n,x,y,k);
		if(op==3) x=read(),y=read(),printf("%lld\n",(query(1,1,n,x,y)%mod));
	}
	return 0;	
}

10 线段树

problem

区间修改,区间平均数,区间方差。

solution

code

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
int read(){
	int a=0,op=1;char c;c=getchar();
	while(c<'0'||c>'9'){if(c=='-')op=-1;c=getchar();}
	while(c>='0'&&c<='9'){a*=10,a+=c^48,c=getchar();}
	return a*op;	
}
int n,m;
double a[maxn],tree1[maxn<<2],tree2[maxn<<2];
double lazy[maxn<<2];
void pushup(int p){
	tree1[p]=tree1[p<<1]+tree1[p<<1|1];
	tree2[p]=tree2[p<<1]+tree2[p<<1|1];
}
void build(int p,int l,int r){
	if(l==r) {tree1[p]=a[l],tree2[p]=a[l]*a[l];return ;}
	int mid=l+r>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
	pushup(p);
}
void tag(int p,int l,int r,double k){
	lazy[p]+=k;
	tree2[p]+=(r-l+1)*k*k+tree1[p]*2*k;
	tree1[p]+=(r-l+1)*k;
}
void pushdown(int p,int l,int r){
	int mid=l+r>>1;
	tag(p<<1,l,mid,lazy[p]);
	tag(p<<1|1,mid+1,r,lazy[p]);
	lazy[p]=0;
}
void modify(int p,int l,int r,int nl,int nr,double k){
	if(nl<=l&&r<=nr){tag(p,l,r,k);return ;}
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) modify(p<<1,l,mid,nl,nr,k);
	if(nr>mid) modify(p<<1|1,mid+1,r,nl,nr,k);
	pushup(p);
}
double query1(int p,int l,int r,int nl,int nr){
	double res=0;
	if(nl<=l&&r<=nr) return tree1[p];
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) res+=query1(p<<1,l,mid,nl,nr);
	if(nr>mid) res+=query1(p<<1|1,mid+1,r,nl,nr);
	return res;
}
double query2(int p,int l,int r,int nl,int nr){
	double res=0;
	if(nl<=l&&r<=nr) return tree2[p];
	int mid=l+r>>1;
	pushdown(p,l,r);
	if(nl<=mid) res+=query2(p<<1,l,mid,nl,nr);
	if(nr>mid) res+=query2(p<<1|1,mid+1,r,nl,nr);
	return res;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int op,x,y;double k;
		op=read();
		if(op==1){
			x=read(),y=read();
			scanf("%lf",&k);
			modify(1,1,n,x,y,k);
		}
		if(op==2){
			x=read(),y=read();
			double tmp=query1(1,1,n,x,y);
			double ans=tmp/((y-x+1)*1.0);
			printf("%.4lf\n",ans);
		}
		if(op==3){
			x=read(),y=read();
			double tmp1=query1(1,1,n,x,y),tmp2=query2(1,1,n,x,y);
			double ans1=tmp2/((y-x+1)*1.0),ans2=tmp1/((y-x+1)*1.0);
			ans2=ans2*ans2;
			double ans=ans1-ans2;
			printf("%.4lf\n",ans);
		}
	}
	return 0;
}
posted @ 2020-10-19 16:32  刘子闻  阅读(183)  评论(0编辑  收藏  举报