数据结构——线段树学习

线段树的基础知识

线段树概念

线段树是基于分治思想的二叉树,维护区间信息,在logn的时间执行区间修改和区间查询。可维护的区间信息包括区间和,区间最值,区间gcd等等。

线段树的创建

在这里插入图片描述
p是二叉树的下标,下标从1开始,l,r分别表示所维护区间的左右端点(w数组的左右区间下标),sum表示当前节点维护区间的总和。叶子节点存放元素本身,非叶子节点存储区间内元素统计值。
w[10]={5,2,0,1,9,7,2,1,2,3}
流程:
1.赋值节点p
2.如果是叶子节点返回,否则裂开进入左右子树。
3.更新需要维护的区间和,左右子树区间和相加即可

线段树空间

开4*N

点修改

在这里插入图片描述

区间修改

在这里插入图片描述

区间查询

在这里插入图片描述

总体函数

在这里插入图片描述
参考:董晓算法
讲得真的非常棒,宝藏up。

练习

P3374

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
const int N=5e5+10;
typedef long long ll;
int n,m,q,x,y,w[N];
struct tree{
	int l,r,sum;
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void build(int p,int l,int r){
	tr[p]={l,r,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
	return;
}

void update(int p,int x,int k){
//	debug(p);
	if(tr[p].l==x&&tr[p].r==x){
		tr[p].sum+=k;
		return;
	}
	int m=tr[p].l+tr[p].r>>1;
//	debug(m);
	if(x<=m) update(lc,x,k);
	if(x>m) update(rc,x,k);//这里不是y,点修改不是区间修改 
	pushup(p);
}
int query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].l+tr[p].r>>1;
	int sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		cin>>q>>x>>y;
		
		if(q==1){
			update(1,x,y);
//			debug(q);
		}else{
			cout<<query(1,x,y)<<endl;
		}
	}
	return 0;
}

P3372

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=1e5+10;
int n,m,q,x,y,k;
ll w[N];

struct tree{
	int l,r,add;
	ll sum;
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于 
		tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
		tr[lc].add+=tr[p].add;
		tr[rc].add+=tr[p].add;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y,int k){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
		tr[p].add+=k;
		return;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y,k);
	if(y>m) update(rc,x,y,k);
	pushup(p);
}
ll query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	ll sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		cin>>q>>x>>y;
		if(q==1){
			cin>>k;
			update(1,x,y,k);
		}else{
			cout<<query(1,x,y)<<endl;
		}
	}
	return 0;
}

总结:
1.一共五个函数。build(p,l,r),pushup(int p),pushdown(int p),update(p,x,y,k),query(p,x,y)。pushdown和update有联系,update不用裂开的时候按照条件更改当前节点权值,pushdown更改左右子节点的权值,相同的操作。
2.
//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。

P3386

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];

struct tree{
	int l,r,add;
	ll sum;
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于 
		tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
		tr[lc].add+=tr[p].add;
		tr[rc].add+=tr[p].add;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y,int k){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
		tr[p].add+=k;
		return;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y,k);
	if(y>m) update(rc,x,y,k);
	pushup(p);
}
ll query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	ll sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		cin>>q;
		if(q==1){
			cin>>x>>y>>k;
			update(1,x,y,k);
		}else{
			cin>>x;
			cout<<query(1,x,x)<<endl;
		}
	}
	return 0;
}

总结:上面三个题目都是一样的,学会区间修改,区间查询,三道题就是一道题。

P1816

在这里插入图片描述

分析:此题的维护的权值为区间内的最小值。

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];

struct tree{
	int l,r,add;
	ll sum;//表示区间(l,r)内最小值 
}tr[4*N];

void pushup(int p){
	tr[p].sum=min(tr[lc].sum,tr[rc].sum);
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].sum+=tr[p].add; 
		tr[rc].sum+=tr[p].add;
		tr[lc].add+=tr[p].add;
		tr[rc].add+=tr[p].add;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y,int k){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].sum+=k;
		tr[p].add+=k;
		return;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y,k);
	if(y>m) update(rc,x,y,k);
	pushup(p);
}
ll query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	ll sum=1e15;
	if(x<=m) sum=min(sum,query(lc,x,y));
	if(y>m) sum=min(sum,query(rc,x,y));
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		cin>>x>>y;
		cout<<query(1,x,y)<<" ";
	}
	return 0;
}

P2574

在这里插入图片描述

分析:本题维护的权值为区间内1的个数。update修改的操作为将1变为0,将0变为1,与1异或。

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];

struct tree{
	int l,r,add;
	ll sum;//表示区间(l,r)内1的个数 
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].sum=(tr[lc].r-tr[lc].l+1-tr[lc].sum);//将1变成0,将0变成1 
		tr[rc].sum=(tr[rc].r-tr[rc].l+1-tr[rc].sum);
		tr[lc].add^=1;
		tr[rc].add^=1;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].sum=(tr[p].r-tr[p].l+1-tr[p].sum);//这里update的操作和pushdow的操作一样 
		tr[p].add^=1;
		return;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y);
	if(y>m) update(rc,x,y);
	pushup(p);
}
ll query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	ll sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%1d",&w[i]);//注意输入技巧 
	build(1,1,n);
	while(m--){
		cin>>q>>x>>y;
		if(q==0){
			update(1,x,y);
		}else{
			cout<<query(1,x,y)<<endl;
		}
	}
	return 0;
}

P2846

在这里插入图片描述

和上一题一样,关着的灯表示状态0,开着的灯表示状态1,求区间内开着的灯的数量。

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
typedef long long ll;
const int N=5e5+10;
int n,m,q,x,y,k;
ll w[N];

struct tree{
	int l,r,add;
	ll sum;//表示区间(l,r)内1的个数 
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].sum=(tr[lc].r-tr[lc].l+1-tr[lc].sum);//将1变成0,将0变成1 
		tr[rc].sum=(tr[rc].r-tr[rc].l+1-tr[rc].sum);
		tr[lc].add^=1;
		tr[rc].add^=1;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].sum=(tr[p].r-tr[p].l+1-tr[p].sum);//这里update的操作和pushdow的操作一样 
		tr[p].add^=1;
		return;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y);
	if(y>m) update(rc,x,y);
	pushup(p);
}
ll query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	ll sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) w[i]=0; 
	build(1,1,n);
	while(m--){
		cin>>q>>x>>y;
		if(q==0){
			update(1,x,y);
		}else{
			cout<<query(1,x,y)<<endl;
		}
	}
	return 0;
}

P1471

在这里插入图片描述

技巧

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
//#define int double//double类型不能当下标 
typedef long long ll;
const int N=1e5+10;
int n,m,q,x,y;
double k;
double w[N];

struct tree{
	int l,r;
	double add,sum,sum2;//sum表示区间内数之和,sum2表示区间内数平方和 
}tr[4*N];

void pushup(int p){
	tr[p].sum=tr[lc].sum+tr[rc].sum;
	tr[p].sum2=tr[lc].sum2+tr[rc].sum2;
}
void pushdown(int p){//相当于在左右子树进行操作,操作数为k 
	if(tr[p].add){
		//要先更新sum2,不然先更新sum,会影响sum2的更新,tr[lc].sum tr[rc].sum
		tr[lc].sum2+=(2.0*tr[p].add*tr[lc].sum+(tr[lc].r-tr[lc].l+1)*tr[p].add*tr[p].add);
		tr[rc].sum2+=(2.0*tr[p].add*tr[rc].sum+(tr[rc].r-tr[rc].l+1)*tr[p].add*tr[p].add);
		
		tr[lc].sum+=(tr[lc].r-tr[lc].l+1)*tr[p].add;//加等于 
		tr[rc].sum+=(tr[rc].r-tr[rc].l+1)*tr[p].add;
		
		tr[lc].add+=tr[p].add;
		tr[rc].add+=tr[p].add;
		tr[p].add=0; 
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,0,w[l],w[l]*w[l]};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}

//修改和查询都是先判断当前节点区间是否被(x,y)覆盖了
//覆盖了进行操作后直接返回 
//没覆盖裂开,向下展开懒标记,然后根据条件进入左右子树,最后回到当前节点进行操作后离开。 
void update(int p,int x,int y,double k){//此处参数为double 
	if(x<=tr[p].l&&tr[p].r<=y){
		//要先更新sum2,不然先更新sum,会影响sum2的更新,tr[lc].sum tr[rc].sum
		tr[p].sum2+=(2.0*k*tr[p].sum+(tr[p].r-tr[p].l+1)*k*k);
		
		tr[p].sum+=(tr[p].r-tr[p].l+1)*k;
		tr[p].add+=k;
		return;
	}
//	debug(p);
	int m=tr[p].r+tr[p].l>>1;
//	debug(m);
	pushdown(p);
	if(x<=m) update(lc,x,y,k);
	if(y>m) update(rc,x,y,k);
	pushup(p);
//	debug(tr[p].sum);
//	debug(tr[p].sum2);
//	cout<<endl;
}
double query(int p,int x,int y){//返回区间数和 
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	double sum=0;
	if(x<=m) sum+=query(lc,x,y);
	if(y>m) sum+=query(rc,x,y);
	return sum;
}

double query2(int p,int x,int y){//返回区间数平方和,注意是int类型的,不然全寄了 
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].sum2;
	}
	int m=tr[p].r+tr[p].l>>1;
	pushdown(p);
	double sum=0;
	if(x<=m) sum+=query2(lc,x,y);
	if(y>m) sum+=query2(rc,x,y);
	return sum;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i];
	build(1,1,n);
	while(m--){
		cin>>q>>x>>y;
		if(q==1){
			cin>>k;
			update(1,x,y,k);
		}else if(q==2){
			double ans=1.0*query(1,x,y)/(y-x+1);
			printf("%.4lf\n",ans);
		}else{
			double ans=1.0*query2(1,x,y)/(y-x+1)-1.0*query(1,x,y)/(y-x+1)*query(1,x,y)/(y-x+1);
			printf("%.4lf\n",ans);
		}
	}
	return 0;
}

注意需要先更新sum2再更新sum,因为sum2需要用到sum,先更新sum会导致值变化从而使得sum2求错了。另外注意精度,query函数返回的类型应该是double类型,不然都寄了。

CF527C

在这里插入图片描述

线段树解法:求一段区间内最长相邻1的个数,lmx,rmx,mx分别表示这段区间左边最长,右边最长,区间最长连续1的个数。1表示没被砍,0表示被砍,因为区间权值代表的是长度,所以在每个单元中间再插入一个单元,表示被砍的单元。所以真实最长为(mx+1)/2。

在这里插入图片描述

#include<bits/stdc++.h>
#define ll long long
#define pl p<<1
#define pr p<<1|1
using namespace std;
int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
const int N=4e5+10;
int w,h,n,x;
char op;
struct Tree{
	int l,r;
	int lmx,rmx,mx;
}s[2][4*N];
void pushup(int k,int p){
	s[k][p].lmx=s[k][pl].lmx;
	if(s[k][pl].lmx==s[k][pl].r-s[k][pl].l+1)
		s[k][p].lmx=max(s[k][p].lmx,s[k][pl].lmx+s[k][pr].lmx);
	s[k][p].rmx=s[k][pr].rmx;
	if(s[k][pr].rmx==s[k][pr].r-s[k][pr].l+1)
		s[k][p].rmx=max(s[k][p].rmx,s[k][pr].rmx+s[k][pl].rmx);
	s[k][p].mx=max(max(s[k][pl].mx,s[k][pr].mx),max(s[k][p].lmx,s[k][p].rmx));
	s[k][p].mx=max(s[k][p].mx,s[k][pl].rmx+s[k][pr].lmx);
}
void build(int k,int p,int l,int r){
	s[k][p].l=l;s[k][p].r=r;
	if(l==r){
		s[k][p].mx=s[k][p].lmx=s[k][p].rmx=1;
		return;
	}
	int mid=(l+r)>>1;
	build(k,pl,l,mid);
	build(k,pr,mid+1,r);
	pushup(k,p);
}
void change(int k,int p,int x){
	if(s[k][p].l==s[k][p].r){
		s[k][p].mx=s[k][p].lmx=s[k][p].rmx=0;
		return;
	}
	int mid=(s[k][p].l+s[k][p].r)>>1;
	if(x<=mid)
		change(k,pl,x);
	else
		change(k,pr,x);
	pushup(k,p);
}
ll ask(){
	return (ll)(s[0][1].mx+1)/2*(s[1][1].mx+1)/2;
}
int main()
{
    w=read();h=read();n=read();
	build(0,1,1,2*w-1);
	build(1,1,1,2*h-1);
	for(int i=1;i<=n;i++){
		cin>>op;
      x=read();
		if(op=='H')
			change(1,1,2*x);
		else
			change(0,1,2*x);
		cout<<ask()<<endl;
	}
	return 0;
}

set,multiset解法:

在这里插入图片描述

#include <bits/stdc++.h>
#define int long long
using namespace std;
int w,h,n;
multiset<int> x,y,lx,ly;//存储所有碎片的边长、切割的位置 
multiset<int>::iterator it;
signed main()
{
	cin >> w >> h >> n;//最开始是一整块玻璃
	x.insert(w);
	y.insert(h);
	lx.insert(0);
	lx.insert(w);
	ly.insert(0);
	ly.insert(h);
	while(n--)
	{
		char ch;
		int pos;
		cin >> ch >> pos;
		if (ch=='H')
		{
			int l,r;
			ly.insert(pos);
			it=ly.find(pos);
			it--;//找到pos前一个切割位置
			l=*it;
			it++;
			it++;//找到pos后一个切割位置
			r=*it;//删除一个高度r-l,增加两个新高度
			it=y.find(r-l);
			y.erase(it);
			y.insert(r-pos);
			y.insert(pos-l);
		}
		else
		{
			int l,r;
			lx.insert(pos);
			it=lx.find(pos);
			it--;//找到pos前一个切割位置
			l=*it;
			it++;
			it++;//找到pos后一个切割位置
			r=*it;//删除一个宽度r-l,增加两个新宽度
			it=x.find(r-l);
			x.erase(it);
			x.insert(r-pos);
			x.insert(pos-l);
		}
		int r,c;
		it=x.end();
		it--;
		r=*it;
		it=y.end();
		it--;
		c=*it;
		cout << r*c << "\n";
	}
	return 0;
}

P2894

在这里插入图片描述
还是一样的,和上题类似,比上题还简单一点。空房子标记为1,住人房子标记为0,求连续空房子的个数,即连续1的个数。

在这里插入图片描述

#include <cstdio>
#define ls x << 1
#define rs x << 1 | 1
#define INF 0x3f3f3f3f

const int MAXN = 50000;
int n,m;
struct node{
	int l , r , f;
	int num , lnum , rnum;
}Tree[ 4 * MAXN + 5 ];

int Max( int x , int y ) {
	return x > y ? x : y;
}
int Min( int x , int y ) {
	return x < y ? x : y;
}

void Build( int x , int l , int r ) {
	Tree[ x ].l = l , Tree[ x ].r = r , Tree[ x ].f = 2;
	Tree[ x ].lnum = Tree[ x ].rnum = Tree[ x ].num = Tree[ x ].r - Tree[ x ].l + 1;
	if( l == r ) return;
	
	int Mid = ( l + r ) / 2;
	Build( ls , l , Mid );
	Build( rs , Mid + 1 , r );
}
void pushup( int x ) {
	Tree[ x ].lnum = Tree[ ls ].lnum;
	if( Tree[ ls ].num == Tree[ ls ].r - Tree[ ls ].l + 1 ) Tree[ x ].lnum = Tree[ ls ].num + Tree[ rs ].lnum;
	
	Tree[ x ].rnum = Tree[ rs ].rnum;
	if( Tree[ rs ].num == Tree[ rs ].r - Tree[ rs ].l + 1 ) Tree[ x ].rnum = Tree[ rs ].num + Tree[ ls ].rnum;
	
	Tree[ x ].num = Max( Max( Tree[ ls ].num , Tree[ rs ].num ) , Tree[ ls ].rnum + Tree[ rs ].lnum );
}
void pushdown( int x ) {
// 	if( Tree[ x ].l == Tree[ x ].r )
// 		return;
	if( Tree[ x ].f == 0 ) {
		Tree[ ls ].num = 0 , Tree[ ls ].f = 0;
		Tree[ rs ].num = 0 , Tree[ rs ].f = 0;
		Tree[ ls ].lnum = Tree[ ls ].rnum = 0;
		Tree[ rs ].lnum = Tree[ rs ].rnum = 0;
		Tree[ x ].f = 2;
	}
	if( Tree[ x ].f == 1 ) {
		Tree[ ls ].num = Tree[ ls ].r - Tree[ ls ].l + 1 , Tree[ ls ].f = 1;
		Tree[ rs ].num = Tree[ rs ].r - Tree[ rs ].l + 1 , Tree[ rs ].f = 1;
		Tree[ ls ].lnum = Tree[ ls ].rnum = Tree[ ls ].num;
		Tree[ rs ].lnum = Tree[ rs ].rnum = Tree[ rs ].num;
		Tree[ x ].f = 2;
	}
}
void Insert( int x , int l , int r , int k ) {
	if( l > Tree[ x ].r || Tree[ x ].l > r )
		return;
	if( l <= Tree[ x ].l && Tree[ x ].r <= r ) {
		Tree[ x ].f = k;
		Tree[ x ].num = k == 1 ? Tree[ x ].r - Tree[ x ].l + 1 : 0;
		Tree[ x ].lnum = Tree[ x ].rnum = Tree[ x ].num;
		return;
	}
	pushdown( x );
	Insert( ls , l , r , k );
	Insert( rs , l , r , k );
	pushup( x );
}
int Find( int x , int k ) {
	if( Tree[ x ].l == Tree[ x ].r ) return Tree[ x ].l;
	pushdown( x );
	if( Tree[ ls ].num >= k ) return Find( ls , k );
	if( Tree[ ls ].rnum + Tree[ rs ].lnum >= k ) return Tree[ ls ].r - Tree[ ls ].rnum + 1;
	if( Tree[ rs ].num >= k ) return Find( rs , k );
}
int main( ) {
	//freopen("hotel.in","r",stdin);
	//freopen("hotel.out","w",stdout);
	
	scanf("%d %d",&n,&m);
	Build( 1 , 1 , n );
	int op,x,d;
	for( int i = 1 ; i <= m ; i ++ ) {
		scanf("%d",&op);
		if( op == 1 ) {
			scanf("%d",&d);
			if( Tree[ 1 ].num < d ) {
				printf("0\n");
				continue;
			}
			int r = Find( 1 , d );
			Insert( 1 , r , r + d - 1 , 0 );
			printf("%d\n",r);
		}
		else if( op == 2 ) {
			scanf("%d %d",&x,&d);
			Insert( 1 , x , x + d - 1 , 1 );//
		}
	}
	return 0;
}

天梯L3-017森森快递

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

思路:线段树维持的属性是区间内最小值,每次查询区间最小值,答案加上最小值,然后修改这段区间,让这段区间内的所有数都减去最小值。注意还要配合贪心。 即根据右端点从小到大排序,右端点相同的情况,根据左端点从大到小排序。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
#define lc p<<1
#define rc p<<1|1
#define debug(x) cout<<#x<<" = "<<x<<endl
#define int long long
int n,q,w[N],x;

struct node{
	int l,r,var,add;
}tr[4*N];
struct eg{
	int l,r;
	bool operator<(const eg& w) const{
		if(r!=w.r) return r<w.r;
		return l>w.l;
	}
}e[N];
void pushup(int p){
	tr[p].var=min(tr[lc].var,tr[rc].var);
}
void pushdown(int p){
	if(tr[p].add){
		tr[lc].var+=tr[p].add;
		tr[rc].var+=tr[p].add;
		tr[lc].add+=tr[p].add;
		tr[rc].add+=tr[p].add;
		tr[p].add=0;
	}
}
void build(int p,int l,int r){
	tr[p]={l,r,w[l],0};
	if(l==r) return;
	int m=l+r>>1;
	build(lc,l,m);
	build(rc,m+1,r);
	pushup(p);
}
int query(int p,int x,int y){
	if(x<=tr[p].l&&tr[p].r<=y){
		return tr[p].var;
	}
	int m=tr[p].l+tr[p].r>>1;
	pushdown(p);
	int ans=0x3f3f3f3f3f3f3f3f;//这个要初始化64位,不然最后一个测试点过不去
	if(x<=m) ans=min(ans,query(lc,x,y));
	if(y>m) ans=min(ans,query(rc,x,y));
	return ans;
}
void update(int p,int x,int y,int k){
	if(x<=tr[p].l&&tr[p].r<=y){
		tr[p].var+=k;
		tr[p].add+=k;
		return;
	}
	int m=tr[p].l+tr[p].r>>1;
	pushdown(p);
	if(x<=m) update(lc,x,y,k);
	if(y>m) update(rc,x,y,k);
	pushup(p);
}
signed main(){
	cin>>n>>q;
	for(int i=1;i<=n-1;i++) cin>>w[i];
	build(1,1,n-1);
	for(int i=1;i<=q;i++){
		int l,r;cin>>l>>r;
		if(l>r) swap(l,r);//大小顺序 
		e[i]={l,r};
	}
	sort(e+1,e+q+1);
	int ans=0;
	for(int i=1;i<=q;i++){
		int l=e[i].l+1,r=e[i].r;//l注意要加1,因为建树区间是从1~n-1
		int num=query(1,l,r);
		if(num>0){
			ans+=num;
			update(1,l,r,-num);
		}
	}
	cout<<ans<<endl;
	return 0;
}
  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值