线段树的区间合并问题

1.poj1823

题目大意

 有一家酒店有n个房间,3种操作:1.从房间m开始连续有k个房间有人入住 2.从房间m开始连续空出k个房间 3.老板问你最长一段连续空房间的长度。

思路

在线段树上我们还要加一些东西,kl表示这个线段树上点表示的线段从左边开始的连续空房间数,kr表示这个线段树上点表示的线段从右边开始的连续空房间数,sum表示这一段里最大连续空房间数,这样我们就可以得到:sum[i]=max(r[i*2]+l[i*2+1],max(sum[i*2],sum[i*2+1]));

例子: 0 1 0 0 0 0 1 0 0这样一段,kl=1,kr=2,sum=4。

那么查询的时候直接输出sum[1]即可,很方便。

代码

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<climits>
using namespace std;
int n,m;
int kl[64001],kr[64001],sum[64001],laz[64001];
//laz:1表示全住,2表示全不住。
void pushdown(int s,int t,int i,int j){
	int mid=(s+t)/2;
	if(j==2){
		kl[i*2]=mid-s+1;kl[i*2+1]=t-mid;
		kr[i*2]=mid-s+1;kr[i*2+1]=t-mid;
		sum[i*2]=mid-s+1;sum[i*2+1]=t-mid;
		laz[i*2]=2;laz[i*2+1]=2;
	}
	else {
		kl[i*2]=0;kl[i*2+1]=0;
		kr[i*2]=0;kr[i*2+1]=0;
		sum[i*2]=0;sum[i*2+1]=0;
		laz[i*2]=1;laz[i*2+1]=1;
	}
}
void chan(int l,int r,int s,int t,int i,int j){
	int mid=(s+t)/2,tmp=0;
	if(l<=s&&t<=r){
		if(j==2){kl[i]=t-s+1;kr[i]=t-s+1;sum[i]=t-s+1;laz[i]=2;}
		else {kl[i]=0;kr[i]=0;sum[i]=0;laz[i]=1;}
		return;
	}
	if(s==t)return;
	if(laz[i]>0)pushdown(s,t,i,laz[i]);
	laz[i]=0;
	if(l<=mid)chan(l,r,s,mid,i*2,j);
	if(mid+1<=r)chan(l,r,mid+1,t,i*2+1,j);
	kl[i]=kl[i*2];
	if(kl[i*2]==mid-s+1)kl[i]+=kl[i*2+1];
	kr[i]=kr[i*2+1];
	if(kr[i*2+1]==t-mid)kr[i]+=kr[i*2];
	sum[i]=max(kr[i*2]+kl[i*2+1],max(sum[i*2],sum[i*2+1]));
}
int main()
{
    int i,j,x,l,r;
    scanf("%d%d",&n,&m);
    laz[1]=2;kl[1]=n;kr[1]=n;sum[1]=n;
    for(i=1;i<=m;i++){
    	scanf("%d",&x);
    	if(x==1){scanf("%d%d",&l,&r);r=l+r-1;chan(l,r,1,n,1,1);}
    	else if(x==2){scanf("%d%d",&l,&r);r=l+r-1;chan(l,r,1,n,1,2);}
    	else if(x==3)printf("%d\n",sum[1]);
    }
    return 0;
}


2.poj3667

题目描述

有一家酒店有n个房间,有2种操作:1.找到左端点最小的一段长度为m连续空房间入住旅客,并输出左端点。2.空出一段左端点为m长度为k的房间

思路

和上一题十分相似,就是查询操作麻烦一些。先找左子树,再看中间这一段是不是可行,如果可行,把这一段住满,直接返回。最后找右子树。

代码

#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<queue>
#include<climits>
using namespace std;
int n,m;
int sum[200001],kl[200001],kr[200001],laz[200001];
//1:表示全空,2表示全不空
void pushdown(int s,int t,int i,int bb){//lazy标记下传
	int mid=(s+t)/2;
	if(bb==1){
		sum[i*2]=mid-s+1;sum[i*2+1]=t-mid;
		kl[i*2]=mid-s+1;kl[i*2+1]=t-mid;
		kr[i*2]=mid-s+1;kr[i*2+1]=t-mid;
		laz[i*2]=1;laz[i*2+1]=1;
	}
	else {
		sum[i*2]=0;sum[i*2+1]=0;
		kl[i*2]=0;kl[i*2+1]=0;
		kr[i*2]=0;kr[i*2+1]=0;
		laz[i*2]=2;laz[i*2+1]=2;
	}
	laz[i]=0;
}
void emp(int l,int r,int s,int t,int i){//空出
	if(l<=s&&t<=r){laz[i]=1;sum[i]=t-s+1;kl[i]=t-s+1;kr[i]=t-s+1;return;}
	if(s==t)return;
	int mid=(s+t)/2;
	if(laz[i]>0)pushdown(s,t,i,laz[i]);
	if(l<=mid)emp(l,r,s,mid,i*2);
	if(mid+1<=r)emp(l,r,mid+1,t,i*2+1);
	kl[i]=kl[i*2];
	if(kl[i*2]==mid-s+1)kl[i]+=kl[i*2+1];
	kr[i]=kr[i*2+1];
	if(kr[i*2+1]==t-mid)kr[i]+=kr[i*2];
	sum[i]=max(kr[i*2]+kl[i*2+1],max(sum[i*2],sum[i*2+1]));
}
void init(int l,int r,int s,int t,int i){//入住
	if(l<=s&&t<=r){laz[i]=2;sum[i]=0;kl[i]=0;kr[i]=0;return;}
	if(s==t)return;
	int mid=(s+t)/2;
	if(laz[i]>0)pushdown(s,t,i,laz[i]);
	if(l<=mid)init(l,r,s,mid,i*2);
	if(mid+1<=r)init(l,r,mid+1,t,i*2+1);
	kl[i]=kl[i*2];
	if(kl[i*2]==mid-s+1)kl[i]+=kl[i*2+1];
	kr[i]=kr[i*2+1];
	if(kr[i*2+1]==t-mid)kr[i]+=kr[i*2];
	sum[i]=max(kr[i*2]+kl[i*2+1],max(sum[i*2],sum[i*2+1]));
}
int find(int num,int s,int t,int i){//寻找区间
	if(sum[i]<num)return 0;
	int mid=(s+t)/2,ss,tt;
	if(laz[i]>0)pushdown(s,t,i,laz[i]);
	if(sum[i*2]>=num)return find(num,s,mid,i*2);//找左子树
	if(kr[i*2]+kl[i*2+1]>=num){//找中间
		ss=mid-kr[i*2]+1;tt=ss+num-1;
		init(ss,tt,1,n,1);//入住
		return ss;
	}
	if(sum[i*2+1]>=num)return find(num,mid+1,t,i*2+1);//找右子树
	return 0;
}
int main()
{
    int i,j,x,y,z;
    scanf("%d%d",&n,&m);
    sum[1]=n;kl[1]=n;kr[1]=n;laz[1]=1;
    for(i=1;i<=m;i++){
    	scanf("%d",&x);
    	if(x==1){
    		scanf("%d",&y);printf("%d\n",find(y,1,n,1));
    	}
    	else {scanf("%d%d",&y,&z);z=y+z-1;emp(y,z,1,n,1);}
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值