CF484E Sign on Fence(线段树合并+二分+主席树)

本文介绍了如何运用主席树解决一种特殊的区间查询问题:在给定的数列中,对于每次询问,找到区间内长度为k的子区间,使得该子区间的最小值最大,并给出最大值。文章详细解析了问题的解决思路,包括整体二分、线段树合并、单点修改等关键步骤,并提供了两种不同的代码实现,分别是结构体和非结构体的版本。
摘要由CSDN通过智能技术生成
  • CF484E Sign on Fence

题目链接

题目大意:

给定一个长度为n的数列,有m次询问,询问形如l r k
要你在区间[l,r]内选一个长度为k的区间,求区间最小数的最大值

题解

做这种题有套路:
(1)最小值最大化,且具有单调性 ——> 整体二分的思路
(2)根据二分答案,所选数为 x 时,大于 x 的贡献为1,反之为0
(3)要使连续的区间都大于等于 x (也是所选的最小数),且长度至少为k,就等同于 在查询区间内找长度至少为k的连续 1 的子区间。——> 线段树合并的套路
(4)对每种情况建线段树不现实,可以发现,一个数与它刚小一点的数只有一个位置要修改——>主席树,单点修改

线段树合并就是加两个变量:最左/右连续 1 的长度
二分后,分大于小于后,贡献为1与0,其实在其它题,贡献会是1与-1,具体情况具体分析。

我写了两种写法,一个结构体,一个没用结构体

ps:写三目运算符一定要加括号(一开始我就错这里,w了好几发才发现的)

代码一:t[i].ls=t[l].ls+((t[l].ls==(len-len/2))?t[r].ls:0);
代码二:t[i].ls=t[l].ls+(t[l].ls==(len-len/2))?t[r].ls:0;

这两串代码意思完全不同,代码一的条件是(t[l].ls==(len-len/2)。代码二的条件是t[l].ls+(t[l].ls==(len-len/2)),代码二中t[l].ls就不会单独加进去了。

还有,左区间的区间长度要比右区间的大,所以左区间是len-len/2,不要写反

const int maxn=2e5+7;
int n,q,tot,rt[maxn];
typedef pair<int,int>pi;
pi a[maxn];
bool cmp(pi x,pi y){	return x.first>y.first;	}
struct node{
	int l,r,ls,rs,sum;
	node(){	ls=rs=l=r=sum=0; }
}t[maxn<<5];
void pushup(int i,int len){
	int l=t[i].l,r=t[i].r;
	t[i].ls=t[l].ls+((t[l].ls==(len-len/2))?t[r].ls:0);
	t[i].rs=t[r].rs+((t[r].rs==(len/2))?t[l].rs:0);
	t[i].sum=max(max(t[l].sum,t[r].sum),t[l].rs+t[r].ls);
}
void update(int &i,int pre,int l,int r,int id){
	i=++tot;	t[i].l=t[pre].l;	t[i].r=t[pre].r;
	if(l==r){
		t[i].ls=t[i].rs=t[i].sum=1;
		return;
	}
	int mid=l+r>>1;
	if(id<=mid)	update(t[i].l,t[pre].l,l,mid,id);
	else	update(t[i].r,t[pre].r,mid+1,r,id);
	pushup(i,r-l+1);
}
node query(int i,int l,int r,int x,int y){
	if(x<=l&&r<=y)	return t[i];
	int mid=l+r>>1,len=r-l+1;
	node L,R,ans;
	if(x<=mid)	L=query(t[i].l,l,mid,x,y);
	if(y>mid)	R=query(t[i].r,mid+1,r,x,y);
	ans.ls=L.ls+((L.ls==len-len/2)?R.ls:0);
	ans.rs=R.rs+((R.rs==len/2)?L.rs:0);
	ans.sum=max(max(L.sum,R.sum),L.rs+R.ls);
	return ans;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i].first=read();
		a[i].second=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)	update(rt[i],rt[i-1],1,n,a[i].second);
	q=read();
	while(q--){
		int x,y,k,l,r;
		x=read();	y=read();	k=read();
		l=1;	r=n;
		while(l<r){
			int mid=l+r>>1;
			if(query(rt[mid],1,n,x,y).sum>=k)	r=mid;
			else	l=mid+1;
		}
		printf("%d\n",a[l].first);
	}
}

写法二

const int maxn=2e5+7;
typedef pair<int,int>pi;
int n,m,cnt;
int rt[maxn],tot,ls[maxn<<5],rs[maxn<<5],sum[maxn<<5];
int p[maxn<<5],q[maxn<<5];
pi a[maxn];
typedef struct node{
	int s,p,q;
	node(){	s=p=q=0; }
}sz;
bool cmp(pi x,pi y){	return x.first>y.first;	}
void pushup(int i,int len){
	p[i]=p[ls[i]]+((p[ls[i]]==len-len/2)?p[rs[i]]:0);
	q[i]=q[rs[i]]+((q[rs[i]]==len/2)?q[ls[i]]:0);
	sum[i]=max(max(sum[ls[i]],sum[rs[i]]),q[ls[i]]+p[rs[i]]);
}
void update(int &i,int pre,int l,int r,int id){
	if(id>r||id<l)	return;
	i=++tot;	ls[i]=ls[pre];	rs[i]=rs[pre];
	if(l==r){
		p[i]=q[i]=sum[i]=1;
		return;
	}
	int mid=l+r>>1;
	update(ls[i],ls[pre],l,mid,id);	update(rs[i],rs[pre],mid+1,r,id);
	pushup(i,r-l+1);
}
sz query(int i,int l,int r,int x,int y){
	sz t;
	if(r<x||l>y)	return t;
	t.s=sum[i];	t.p=p[i];	t.q=q[i];
	if(x<=l&&r<=y)	return t;
	int mid=l+r>>1,len=r-l+1;
	sz L,R,ans;
	L=query(ls[i],l,mid,x,y);
	R=query(rs[i],mid+1,r,x,y);
	ans.p=L.p+((L.p==len-len/2)?R.p:0);
	ans.q=R.q+((R.q==len/2)?L.q:0);
	ans.s=max(max(L.s,R.s),L.q+R.p);
	return ans;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i].first=read();
		a[i].second=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)	update(rt[i],rt[i-1],1,n,a[i].second);
	m=read();
	while(m--){
		int x=read(),y=read(),k=read(),l=1,r=n;
		while(l<r){
			int mid=l+r>>1;
			if(query(rt[mid],1,n,x,y).s>=k)	r=mid;
			else	l=mid+1;
		}
		printf("%d\n",a[l].first);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值