线段树1

基本概念

线段树是一棵二叉树,每个结点对应原序列的一段区间。

应用示例

给定序列,接下来有m次操作:单点修改或区间查询最小值。

算法:建树、查询、更新

示例:洛谷P3374(树状数组模板)

代码
# include<iostream>
using namespace std;
# define ll long long

const int N=5e5+2;
int n,m,op,x,y;
ll a[N],k,t[4*N];

void build(int l,int r,int i){
	if(l==r) t[i]=a[l];
	else{
		int mid=(l+r)/2;
		build(l,mid,2*i);
		build(mid+1,r,2*i+1);
		t[i]=t[2*i]+t[2*i+1];
	}
}

void update(int l,int r,int i,int x,ll k){
	if(x<l||r<x) return;
	if(l==r){
		a[x]+=k;
		t[i]+=k;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(l,mid,2*i,x,k);
	else update(mid+1,r,2*i+1,x,k);
	t[i]+=k;
}//区间[l,r]编号为i,将a[x]加k 

ll sum(int l,int r,int i,int x,int y){
	if(x<=l&&r<=y) return t[i]; //[l,r]包含于[x,y] 
	if(y<l||r<x) return 0; //[l,r]与[x,y]无交集 
	int mid=(l+r)/2;
	ll ans=0;
	if(x<=mid) ans+=sum(l,mid,2*i,x,y); //[x,y]与[l,mid]有交集 
	if(mid<y) ans+=sum(mid+1,r,2*i+1,x,y); //[x,y]与[mid+1,r]有交集 
	return ans;
}//区间[l,r]编号为i,查询区间[x,y] 

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i];
	build(1,n,1);
	while(m--){
		cin>>op;
		if(op-1){
			cin>>x>>y;
			cout<<sum(1,n,1,x,y)<<endl;
		}
		else{
			cin>>x>>k;
			update(1,n,1,x,k);
		}
	}
	return 0;
}

练习

  1. 提高P129

思路:维护一个长为 m m 的线段树,A 操作就在末尾单点修改,Q 操作就区间查询。

代码
# include<iostream>
using namespace std;

const int M=2e5+2;
char op;
int m,p,n,ans=0,l,ma[4*M];

void update(int l,int r,int i,int x,int k){
	if(l==r&&l==x){
		ma[i]=max(ma[i],k);
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(l,mid,2*i,x,k);
	if(mid<x) update(mid+1,r,2*i+1,x,k);
	ma[i]=max(ma[2*i],ma[2*i+1]);
}

int find(int l,int r,int i,int x,int y){
	if(x<=l&&r<=y) return ma[i];
	int mid=(l+r)/2,ans=0;
	if(x<=mid) ans=max(ans,find(l,mid,2*i,x,y));
	if(mid<y) ans=max(ans,find(mid+1,r,2*i+1,x,y));
	return ans;
}

int main(){
	cin>>m>>p;
	for(int i=1;i<=m;++i){
		cin>>op>>l;
		if(op=='A')
			update(1,m,1,++n,(l+ans)%p);
		if(op=='Q'){
			ans=find(1,m,1,n-l+1,n);
			cout<<ans<<endl;
		}
	}
	return 0;
}

  • 数据加强:洛谷P1198

代码
# include<iostream>
using namespace std;
# define ll long long

const int M=2e5+2;
char op;
int m,n,l;
ll p,ans,t,ma[4*M];

void update(int l,int r,int i,int x,ll k){
	if(l==r&&l==x){
		ma[i]=max(ma[i],k);
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(l,mid,2*i,x,k);
	if(mid<x) update(mid+1,r,2*i+1,x,k);
	ma[i]=max(ma[2*i],ma[2*i+1]);
}

int find(int l,int r,int i,int x,int y){
	if(x<=l&&r<=y) return ma[i];
	int mid=(l+r)/2,ans=0;
	if(x<=mid) ans=max(ans,find(l,mid,2*i,x,y));
	if(mid<y) ans=max(ans,find(mid+1,r,2*i+1,x,y));
	return ans;
}

int main(){
	cin>>m>>p;
	for(int i=1;i<=m;++i){
		cin>>op;
		if(op=='A'){
			cin>>t;
			update(1,m,1,++n,(t+ans)%p);
		}
		if(op=='Q'){
			cin>>l;
			ans=find(1,m,1,n-l+1,n);
			cout<<ans<<endl;
		}
	}
	return 0;
}

  1. 洛谷P4588 数学计算

思路:类似上题,维护一个线段树,存储所有操作的乘积,每次执行操作 1 1 就更新线段树的末尾,每次执行操作 2 2 就将对应位置修改为 1 1 ,最后输出 t[1] t[1] 就是答案。注意:一定要开 long long,否则会爆零!

代码
# include<iostream>
using namespace std;
# define ll long long

const int N=1e5+2;
int T,q,n,op,pos,a[N];
ll M,m,t[4*N];

void update(int l,int r,int i,int x,int k){
	if(l==x&&r==x){
		t[i]=k;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(l,mid,2*i,x,k);
	if(mid<x) update(mid+1,r,2*i+1,x,k);
	t[i]=t[2*i]*t[2*i+1]%M;
} //将a[x]修改为k

void solve(){
	cin>>q>>M;
	n=0;
	for(int i=1;i<=4*q;++i) t[i]=1;
	for(int i=1;i<=q;++i){
		cin>>op;
		if(op==1){
			cin>>m;
			a[i]=(++n);
			update(1,q,1,n,m);
			cout<<t[1]<<endl;
		}
		else{
			cin>>pos;
			update(1,q,1,a[pos],1);
			cout<<t[1]<<endl;
		}
	} 
}

int main(){
	cin>>T;
	while(T--) solve();
	return 0;
}

  1. 洛谷P3801 红色的幻想乡

思路:维护两棵线段树,分别存储行和列中有多少是红雾,对于每个操作 2 2 ,利用容斥原理计算答案。记得开 long long

代码
# include<iostream>
using namespace std;
# define ll long long

const int N=1e5+2;
int n,m,q,op,s,t,x1,y1,x2,y2;
int row[4*N],col[4*N];

void update(int* a,int l,int r,int i,int x){
	if(l==x&&r==x){
		a[i]^=1;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid) update(a,l,mid,2*i,x);
	if(mid<x) update(a,mid+1,r,2*i+1,x);
	a[i]=a[2*i]+a[2*i+1];
}

int find(int* a,int l,int r,int i,int x,int y){
	if(x<=l&&r<=y) return a[i];
	int mid=(l+r)/2,ans=0;
	if(x<=mid) ans+=find(a,l,mid,2*i,x,y);
	if(mid<y) ans+=find(a,mid+1,r,2*i+1,x,y);
	return ans;
}

int main(){
	cin>>n>>m>>q;
	while(q--){
		cin>>op;
		if(op==1){
			cin>>s>>t;
			update(row,1,n,1,s);
			update(col,1,m,1,t);
		}
		else{
			cin>>x1>>y1>>x2>>y2;
			s=find(row,1,n,1,x1,x2);
			t=find(col,1,m,1,y1,y2);
			cout<<(ll)s*(y2-y1+1)+(ll)t*(x2-x1+1)-s*t*2<<endl;
		}
	}
	return 0;
}