线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)

文章目录

  • 单点修改
    • 区间求和
    • 区间最值
    • 求逆序对
    • 求区间最大位子
  • 区间修改
    • 成段替换
    • 成段增减区间求和
    • 成段替换简单hash
    • 区间合并
  • 扫描线
    • 矩形面积并
    • 矩形周长并

这篇博客的基础: 线段树(简单实现高效区间操作)

单点修改

线段树的单点修改可以看成一个完整线段树的简化版,它的修改方式相当于在树状数组的基础上附带一个递归到需要修改的点的过程,所以效率略低于树状数组。
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第1张图片
理解区间修改、区间最值及求和的原理后,单点更新的求和及最值问题就直接贴模板了。

区间求和

#include
#include
using namespace std;

int t,n;
int a[50004];
int tree[4*50004];

inline 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];
}
inline int query(int lq,int rq,int l,int r,int p)
{
	int ans=0;
	if(lq<=l&&r<=rq) return tree[p];
//	pushdown(p,l,r,tag[p]);
	int  mid=(l+r)>>1;
	if(lq<=mid) ans+=query(lq,rq,l,mid,p<<1);
	if(rq>mid) ans+=query(lq,rq,mid+1,r,p<<1|1);
	return ans;
}
inline void update(int x,int l,int r,int p,int k)
{
	if( l==r && l==x )
	{
		tree[p]+=k;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(x,l,mid,p<<1,k);
	else  update(x,mid+1,r,p<<1|1,k);
	tree[p]=tree[p<<1]+tree[p<<1|1];
	
}
int main()
{
	scanf("%d",&t);
	for(int p=1;p<=t;++p)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		build(1,1,n); 
		printf("Case %d:\n",p);
		string in;
		while(cin>>in)
		{
			int x, y;
			if(in=="Query") 
			{
				scanf("%d %d",&x,&y);
				printf("%d\n",query(x,y,1,n,1));
			}
			else if(in=="Add")
			{
				scanf("%d %d",&x,&y);
				update(x,1,n,1,y);
			}
			else if(in=="Sub")
			{
				scanf("%d %d",&x,&y);
				update(x,1,n,1,-y); 
			}
			else if(in=="End") break;
		}
	} 
	return 0;
} 

区间最值

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第2张图片

#include
#include
using namespace std;

typedef int ll;
int tree[4*200005];
int a[200005];


inline void build(ll p,ll l,ll r)
{
	if(l==r)
	{
		tree[p]=a[l];
		return;
	}
	ll mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	tree[p]=max(tree[p<<1],tree[p<<1|1]);
}
inline void update(ll x,ll l,ll r,ll p,ll k)
{
	if( l==r && l==x )
	{
		tree[p]=k;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(x,l,mid,p<<1,k);
	else  update(x,mid+1,r,p<<1|1,k);
	tree[p]=max(tree[p<<1],tree[p<<1|1]);
}
inline ll query(ll lq,ll rq,ll l,ll r,ll p)
{
	ll ans=0;
	if(lq<=l&&r<=rq) return tree[p];
	ll mid=(l+r)>>1;
	if(lq<=mid) ans=max(ans,query(lq,rq,l,mid,p<<1));
	if(rq>mid) ans=max(ans,query(lq,rq,mid+1,r,p<<1|1));
	return ans;
}
int main()
{
	int n,m;
	while(scanf("%d %d",&n,&m)!=EOF)
	{
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		build(1,1,n);
		while(m--)
		{
			char num;
			getchar();
			scanf("%c",&num);
			if(num=='U')
			{
				ll x,k;
				scanf("%d %d",&x,&k);
				update(x,1,n,1,k); 
			}
			if(num=='Q')
			{
				ll x,y;
				scanf("%d %d",&x,&y);
				printf("%d\n",query(x,y,1,n,1)); 
			}
		}
	}
	return 0; 
} 

求逆序对

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第3张图片
——题解——
出现过的数字标记为1,未出现的记为0,。我们维护的线段树相当于区间求和,每读入一个数字i,就对区间i~n求一次和,然后把i的位置标记为1
——Code——

#include
#include
#include
#include
using namespace std;

int tree[4*5003];
int a[5003];
int n;

inline void update(int x,int l,int r,int p){
	if( x==l && l==r ){
		tree[p]=1;
		return;
	}
	int mid = (l+r)>>1;
	if( x<=mid ) update(x,l,mid,p<<1);
	else update(x,mid+1,r,p<<1|1);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
inline int query(int lq,int rq,int l,int r,int p){
	int ans=0;
	if( lq<=l && r<=rq ){
		return tree[p];
	}
	int mid = (l+r)>>1;
	if(lq<=mid) ans+=query(lq,rq,l,mid,p<<1);
	if(rq>mid ) ans+=query(lq,rq,mid+1,r,p<<1|1);
	return ans;
} 
int main(){
	while(~scanf("%d",&n)){
		memset(tree,0,sizeof(tree));
		int sum=0;
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			update(a[i],0,n-1,1);
			sum+=query(a[i]+1,n-1,0,n-1,1);
		}
	//	printf("%d\n",sum);
		int ans=sum;
		for(int i=1;i<=n;++i){
			sum+=n-2*a[i]-1;
	//		printf("%d\n",sum);
			ans=min(ans,sum);
		}
		printf("%d\n",ans);
	}
	return 0;
}

求区间最大位子

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第4张图片
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第5张图片
——题解——
这题的意思是不断从左往右寻找能贴海报的位置,需要用一个线段树维护某段区间中最大的空余位置或最小的利用位置。
——Code——

#include
#include
#include
#include
using namespace std;

int h,w,m;
int tree[4*200005];

inline int update(int x,int l,int r,int p){
	int ans=-1;
	if(l==r){
		tree[p]+=x;
		return l;
	}
	int mid = (l+r)>>1;
	if( w-tree[p<<1] >= x) ans=update(x,l,mid,p<<1);
	else if( w-tree[p<<1|1] >= x) ans=update(x,mid+1,r,p<<1|1);
	tree[p]=min(tree[p<<1],tree[p<<1|1]);
	return ans;
}
int main(){
	while(~scanf("%d %d %d",&h,&w,&m)){
		int right=min(h,m);
		memset(tree,0,sizeof(tree));
		for(int i=1;i<=m;++i){
			int req;
			scanf("%d",&req);
			if(req>w){
				printf("-1\n");
				continue;
			}
			printf("%d\n",update(req,1,right,1));
		}
	}
	return 0;
}

区间修改

关于区间修改的问题我在上一篇博客中已经详细说明,下面的一些问题基本是模板或者是模板的简单变形。

成段替换

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第6张图片
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第7张图片
——题解——
在这题中lazytag充当区间属性的作用(即标记1、2、3),线段树维护区间和,答案就是线段树根节点的值。需要注意的是,当初始lazytag值为0时,千万不要向下传导lazytag。
——Code——

#include
#include
#include
#include
using namespace std;

int t,n,m;
int tree[4*100005];
int tag[4*100005];

inline void build(int p,int l,int r){
	if( l==r ){
		tree[p]=1;
		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];
}
inline void pushdown(int p,int l,int r,int k){
	int mid = (l+r)>>1;
	tree[p<<1] = k*(mid-l+1);
	tree[p<<1|1] = k*(r-mid);
	tag[p<<1] = k;
	tag[p<<1|1] = k;
	tag[p]=0;
}
inline void update(int lq,int rq,int l,int r,int p,int k){
	if( lq<=l && r<=rq ){
		tree[p]=k*(r-l+1);
		tag[p]=k;
		return ;
	}
	if(tag[p]) pushdown(p,l,r,tag[p]);
	int mid = (l+r)>>1;
	if(lq<=mid) update(lq,rq,l,mid,p<<1,k);
	if(rq> mid) update(lq,rq,mid+1,r,p<<1|1,k);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
int main(){
	scanf("%d",&t);
	for(int caset=1;caset<=t;++caset){
		memset(tree,0,sizeof(tree));
		memset(tag,0,sizeof(tag));
		scanf("%d %d",&n,&m);
		build(1,1,n);
		for(int i=1;i<=m;++i){
			int x,y,z;
			scanf("%d %d %d",&x,&y,&z);
			update(x,y,1,n,1,z);
		}
		printf("Case %d: The total value of the hook is %d.\n",caset,tree[1]);
	}
	return 0;
}

成段增减区间求和

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第8张图片
——题解——
这是一道完整的线段树模板题,涵盖的线段树所有的操作,包括:建树、区间修改、lazytag传导、区间查询
——Code——

#include
#include
#include
#include
using namespace std;

typedef long long ll;
ll n,m;
ll tree[4*100005];
ll a[100005];
ll tag[4*100005];

inline void build(ll p,ll l,ll r)
{
	if(l==r)
	{
		tree[p]=a[l];
		return;
	}
	ll 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];
}
inline void pushdown(ll p,ll l,ll r,ll k)
{
	ll mid=(l+r)>>1;
	tag[p<<1]+=k;
	tree[p<<1]+=(mid-l+1)*k;
	tag[p<<1|1]+=k;
	tree[p<<1|1]+=(r-mid)*k;
	tag[p]=0;
}
inline ll query(ll lq,ll rq,ll l,ll r,ll p)
{
	ll  ans=0;
	if(lq<=l&&r<=rq) return tree[p];
	pushdown(p,l,r,tag[p]);
	ll  mid=(l+r)>>1;
	if(lq<=mid) ans+=query(lq,rq,l,mid,p<<1);
	if(rq>mid) ans+=query(lq,rq,mid+1,r,p<<1|1);
	return ans;
}
inline void update(ll lq,ll rq,ll l,ll r,ll p,ll k)
{
	if(lq<=l&&r<=rq)
	{
		tree[p]+=k*(r-l+1);
		tag[p]+=k;
		return;
	}
	pushdown(p,l,r,tag[p]); 
	ll mid=(l+r)>>1;
	if(lq<=mid) update(lq,rq,l,mid,p<<1,k);
	if(rq>mid) update(lq,rq,mid+1,r,p<<1|1,k);
	tree[p]=tree[p<<1]+tree[p<<1|1];
}
int main()
{
	scanf("%lld %lld",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%lld",&a[i]);
	build(1,1,n);
	while(m--)
	{
		char in;
		getchar();
//		getchar();
		scanf("%c",&in);
		if(in=='Q')
		{
			ll x,y;
			scanf("%lld %lld",&x,&y);
			printf("%lld\n",query(x,y,1,n,1));
		}
		else
		{
			ll x,y,k;
			scanf("%lld %lld %lld",&x,&y,&k);
			update(x,y,1,n,1,k);
		}
	}
	return 0;
}

成段替换简单hash

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第9张图片
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第10张图片
——题解——
这题是成段替换的一个变式,首先要对海报的区间进行离散化,然后从最后一张海报开始贴,如果能贴上,则返回bool值true,答案数加1
——Code——

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef pair<int,int> pii;
bool tree[4*100005];
pii a[20004];
int id[10000007];
int c,n;
priority_queue<int,vector<int>,greater<int> > q;

inline bool update(int lq,int rq,int l,int r,int p){
	bool flag=false;
	if( tree[p]) return flag;
	if( lq<=l && r<=rq ){
		tree[p] = true;
		return flag = true;
	}
	int mid = (l+r)>>1;
	if( lq<=mid ) flag = update(lq,rq,l,mid,p<<1) || flag;
	if( rq> mid ) flag = update(lq,rq,mid+1,r,p<<1|1) || flag;
	tree[p] = tree[p] || (tree[p<<1] && tree[p<<1|1]);
	return flag;
}
int main(){
	scanf("%d",&c);
	while(c--){
		scanf("%d",&n);
		memset(id,0,sizeof(id));
		memset(tree,0,sizeof(tree));
		for(int i=1;i<=n;++i){
			scanf("%d %d",&a[i].first,&a[i].second);
			q.push(a[i].first);
			q.push(a[i].second);
		}
		int cnt=0;
		while(!q.empty()){
			int v = q.top();
			q.pop();
			if( id[v] ) continue;
			id[v] = ++cnt;
		}
		for(int i=1;i<=n;++i){
			a[i].first = id[a[i].first];
			a[i].second = id[a[i].second];
		}
		int ans=0;
		for(int i=n;i>=1;--i){
			if( update(a[i].first,a[i].second,1,cnt,1) ) ++ans;
		}
		cout<<ans<<endl;
	}
	return 0;
}

区间合并

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第11张图片
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第12张图片
——题解——
这是一道进阶的线段树题。线段树需要维护的是区间最大空余位置,考虑到有可能某连续空区间正好卡在两段自区间之间,所以线段树的维护需要三个值:从左端开始的连续空区间长度lv、从右端开始的连续空区间长度rv、最大空区间长度mv。对于一个区间来讲,它的lv即为左子区间的lv,rv即为右子区间的rv,中间空余区间长度即为左子区间的rv加上右子区间的lv,而mv即为三者中的最大值。
——Code——

#include 
#include 
#include 
#include 
using namespace std;

struct node{
	int rv;
	int lv;
	int mv;
} tree[50004*4];
int tag[50004*4];
int n,m;

inline void pushup(int p,int l,int r){
	int mid = (l+r)>>1;
	tree[p].mv = max( tree[p<<1].rv+tree[p<<1|1].lv, max(tree[p<<1].mv, tree[p<<1|1].mv));
	tree[p].lv = tree[p<<1].lv;
	tree[p].rv = tree[p<<1|1].rv;
	if( tree[p<<1].mv==mid-l+1 )
		tree[p].lv += tree[p<<1|1].lv;
	if( tree[p<<1|1].mv==r-mid )
		tree[p].rv += tree[p<<1].rv;
	
}
inline void pushdown(int p,int l,int r){
	int mid = (l+r)>>1;
	tag[p<<1] = tag[p<<1|1] = tag[p];
	tree[p<<1].mv = tree[p<<1].lv = tree[p<<1].rv = (mid-l+1)*tag[p];
	tree[p<<1|1].mv = tree[p<<1|1].lv = tree[p<<1|1].rv = (r-mid)*tag[p];
	tag[p] = -1;
}
inline void build(int p,int l,int r){
	tree[p].lv = tree[p].rv = tree[p].mv = (r-l+1);
	tag[p] = 1;
	if( l==r ) return;
	int mid = (l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}
inline void update(int lq,int rq,int l,int r,int p,int k){
//	cout<<0<<" ";
	if( lq<=l && r<=rq){
		tree[p].mv = tree[p].lv = tree[p].rv = k*(r-l+1);
		tag[p] = k;
		return;
	}
	if(tag[p]!=-1) pushdown(p,l,r);
	int mid = (l+r)>>1;
	if(lq<=mid) update(lq,rq,l,mid,p<<1,k);
	if(rq> mid) update(lq,rq,mid+1,r,p<<1|1,k);
	pushup(p,l,r);
}
inline int query(int qlength,int l,int r,int p){
	if( tree[p].lv>=qlength ) return l;
	int mid = (l+r)>>1;
	if( tree[p<<1].mv>=qlength) return query(qlength,l,mid,p<<1);
	else if( tree[p<<1].rv+tree[p<<1|1].lv>=qlength ) return mid-tree[p<<1].rv+1;
	else return query(qlength,mid+1,r,p<<1|1);
}
int main(){
	while(~scanf("%d %d",&n,&m)){
		build(1,1,n);
		while(m--){
			int operation;
			scanf("%d",&operation);
			if(operation==1){
				int len;
				scanf("%d",&len);
				if(tree[1].mv<len){
					printf("0\n");
					continue;
				}
				int ans = query(len,1,n,1);
				printf("%d\n",ans);
				update(ans,ans+len-1,1,n,1,0);
//				cout<<"OK"<
			}else{
				int x;
				int d;
				scanf("%d %d",&x,&d);
				update(x,x+d-1,1,n,1,1);
			}
		}
	}
	return 0;
}

扫描线

关于扫面线的讲解,我推荐这篇博客扫描线+线段树
接下去的两题都是上面这篇博客的例题,所以我直接贴代码了。

矩形面积并


——Code——

#include 
#include 
#include 
#include 
using namespace std;

struct Line{
	double l;//左端点 
	double r;//右端点 
	double h;//高度 
	int d;//矩形上边界记为-1,下边界记为1 
} seg[202];//扫描线 
double x[202];//底边坐标 ,用于离散化处理 
double tree[202<<2];//记录边长度的线段树 
int tag[202<<2];//标记一段区间的矩形是否已经被扫描完 
int n,m;

bool cmp(Line p1,Line p2){
	return p1.h < p2.h;
}
inline int binary(int len, double k){//二分查找离散化后 ,端点对应的序号 
	int left = 1;
	int right = len;
	int ret = 0;
	while( left<=right ){
		int mid = (left+right)>>1;
		if( k<x[mid] ){
			right = mid-1;
		}else{
			left = mid+1;
			ret = mid;
		}
	}
	return ret;
}
void pushup(int l,int r,int p){//重置标记,以及计算线扫描线有效线段长度总和 
	if(tag[p]){
		tree[p]=x[r]-x[l-1];
	}else if( l==r ){
		tree[p]=0;
	}else{
		tree[p]=tree[p<<1]+tree[p<<1|1];
	}
}
void update(int lq,int rq,int l,int r,int p,int k){
	if( lq<=l && r<=rq){
		tag[p]+=k;
		pushup(l,r,p);
		return;
	}
	int mid = (l+r)>>1;
	if(lq<=mid) update(lq,rq,l,mid,p<<1,k);
	if(rq> mid) update(lq,rq,mid+1,r,p<<1|1,k);
	pushup(l,r,p);
}

int main(){
	int now=0;
	while(scanf("%d",&n)!=EOF){
		if( n==0 ) break;
		++now;
		int cntline=0;
		memset(tree,0,sizeof(tree));
		memset(tag,0,sizeof(tag));
		for(int i=1;i<=n;++i){
			double x1,x2,y1,y2;
			scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
			seg[++cntline].d = 1;
			seg[cntline].l = x1;
			seg[cntline].r = x2;
			seg[cntline].h = y1;
			x[cntline]=x1;
			seg[++cntline].d = -1;
			seg[cntline].l = x1;
			seg[cntline].r = x2;
			seg[cntline].h = y2;
			x[cntline]=x2;  
		}
		sort(seg+1,seg+cntline+1,cmp);
		sort(x+1,x+cntline+1);		
//		cout<
		int length = unique(x+1,x+cntline+1)-x-1;//离散化,去掉重复的端点
		double ans=0;
		for(int i=1;i<=cntline;++i)
		{
			int lq = binary(length,seg[i].l)+1;
			int rq = binary(length,seg[i].r); 
			update(lq,rq,1,length,1,seg[i].d);
			ans+=(seg[i+1].h-seg[i].h) * tree[1]; 
//			cout<
		}
		printf("Test case #%d\nTotal explored area: %.2f\n\n",now,ans);
	}
	return 0;
}

矩形周长并

线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第13张图片
线段树求解各种问题的模板(单点修改、区间修改、扫描线思想)_第14张图片
——Code——

#include 
#include 
#include 
#include 
#include 
using namespace std;

struct line{
	int l;
	int r;
	int h;
	int d;
}seg[100005];
struct node{
	int len;
	int vh;
	int lv;
	int rv;
	int tag;
}tree[100005<<2];
int x[100005];
int n;
bool cmp(line p1,line p2){
	return p1.h<p2.h;
}
inline int binary(int len, int k){
	int left = 1;
	int right = len;
	int ret = 0;
	while( left<=right ){
		int mid = (left+right)>>1;
		if( k<x[mid] ){
			right = mid-1;
		}else{
			left = mid+1;
			ret = mid;
		}
	}
	return ret;
}
void pushup(int l,int r,int p){
	if(tree[p].tag){
		tree[p].len = x[r]-x[l-1];
		tree[p].lv = tree[p].rv = 1;
		tree[p].vh = 1;
	}else if( l==r ){
		tree[p].len = 0;
		tree[p].lv = tree[p].rv = 0;
		tree[p].vh = 0; 
	}else{
		tree[p].len = tree[p<<1].len+tree[p<<1|1].len;
		tree[p].lv = tree[p<<1].lv;
		tree[p].rv = tree[p<<1|1].rv;
		tree[p].vh = tree[p<<1].vh + tree[p<<1|1].vh - (tree[p<<1].rv&tree[p<<1|1].lv);
	}
}
void update(int lq,int rq,int l,int r,int p,int k){
	if(lq<=l && r<=rq){
		tree[p].tag+=k;
		pushup(l,r,p);
		return;
	}
	int mid = (r+l)>>1;
	if(lq<=mid) update(lq,rq,l,mid,p<<1,k);
	if(rq >mid) update(lq,rq,mid+1,r,p<<1|1,k);
	pushup(l,r,p);
}
int main(){
	while(~scanf("%d",&n)){
		int cnt=0;
		memset(tree,0,sizeof(tree));
		memset(x,0,sizeof(x));
		for(int i=1;i<=n;++i){
			int x1,x2,y1,y2;
			scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
			seg[++cnt].l = x1;
			seg[cnt].r = x2;
			seg[cnt].d = 1;
			seg[cnt].h = y1;
			x[cnt] = x1;
			seg[++cnt].l = x1;
			seg[cnt].r = x2;
			seg[cnt].d = -1;
			seg[cnt].h = y2;
			x[cnt] = x2;
		}
		sort(x+1,x+cnt+1);
		sort(seg+1,seg+cnt+1,cmp);
		int length=unique(x+1,x+cnt+1)-x-1;
		int ans=0,pre=0;
		for(int i=1;i<=cnt;++i){
			int lq = binary(length,seg[i].l)+1;
			int rq = binary(length,seg[i].r);
			update(lq,rq,1,length,1,seg[i].d);
			ans += max(tree[1].len-pre,pre-tree[1].len) + tree[1].vh*(seg[i+1].h-seg[i].h)*2;
			pre = tree[1].len; 
		}
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(线段树求解各种问题的模板(单点修改、区间修改、扫描线思想))