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


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

单点修改

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

区间求和

在这里插入图片描述

#include<cstdio>
#include<cstring>
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;
} 

区间最值

在这里插入图片描述

#include<iostream>
#include<cstdio>
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; 
} 

求逆序对

在这里插入图片描述
——题解——
出现过的数字标记为1,未出现的记为0,。我们维护的线段树相当于区间求和,每读入一个数字i,就对区间i~n求一次和,然后把i的位置标记为1
——Code——

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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;
}

求区间最大位子

在这里插入图片描述
在这里插入图片描述
——题解——
这题的意思是不断从左往右寻找能贴海报的位置,需要用一个线段树维护某段区间中最大的空余位置或最小的利用位置。
——Code——

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
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;
}

区间修改

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

成段替换

在这里插入图片描述
在这里插入图片描述
——题解——
在这题中lazytag充当区间属性的作用(即标记1、2、3),线段树维护区间和,答案就是线段树根节点的值。需要注意的是,当初始lazytag值为0时,千万不要向下传导lazytag。
——Code——

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
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;
}

成段增减区间求和

在这里插入图片描述
——题解——
这是一道完整的线段树模板题,涵盖的线段树所有的操作,包括:建树、区间修改、lazytag传导、区间查询
——Code——

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
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

在这里插入图片描述
在这里插入图片描述
——题解——
这题是成段替换的一个变式,首先要对海报的区间进行离散化,然后从最后一张海报开始贴,如果能贴上,则返回bool值true,答案数加1
——Code——

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <utility>
#include <queue>
#include <vector>
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;
}

区间合并

在这里插入图片描述
在这里插入图片描述
——题解——
这是一道进阶的线段树题。线段树需要维护的是区间最大空余位置,考虑到有可能某连续空区间正好卡在两段自区间之间,所以线段树的维护需要三个值:从左端开始的连续空区间长度lv、从右端开始的连续空区间长度rv、最大空区间长度mv。对于一个区间来讲,它的lv即为左子区间的lv,rv即为右子区间的rv,中间空余区间长度即为左子区间的rv加上右子区间的lv,而mv即为三者中的最大值。
——Code——

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
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"<<endl;
			}else{
				int x;
				int d;
				scanf("%d %d",&x,&d);
				update(x,x+d-1,1,n,1,1);
			}
		}
	}
	return 0;
}

扫描线

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

矩形面积并

在这里插入图片描述
——Code——

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
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<<endl;		
		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<<seg[i+1].h<<" "<<seg[i].h<<" "<<tree[1]<<" "<<ans<<endl;
		}
		printf("Test case #%d\nTotal explored area: %.2f\n\n",now,ans);
	}
	return 0;
}

矩形周长并

在这里插入图片描述
在这里插入图片描述
——Code——

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <utility>
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;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值