【2022省选模拟】麻烦的杂货店——线段树

本文讲解了如何通过单调栈解决求区间内最长连续合法括号序列的问题,涉及线段染色和离线优化策略,利用线段树高效处理区间查询。关键技巧在于分类处理颜色在左端点前后的操作,结合ZKW线段树实现O((n+m)logn)的时间复杂度。
摘要由CSDN通过智能技术生成

此题还是不提供链接

题目描述

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

题解

容易发现这其实就是一个求区间最长连续合法括号序列的括号匹配问题。

我们用单调栈把前后两个能匹配的括号连边,并把连通的位置染成同一种颜色,那么问题就可以转化为,求区间内同种颜色的位置相距的最大距离。

在这里插入图片描述
并且,括号序列还有个很重要的性质,就是相邻同一颜色连成的线段只会包含不会相交。所以对于这样一条线段,其内外的颜色一定是没有交集的,换句话说,里面的位置不会往外连边。

然后我们就可以考虑把询问离线下来,从右往左枚举左端点,然后询问右端点。枚举的过程中,如果你使用线段树维护某个位置为右端点的答案,每次直接考虑加入一个点,暴力更改同一颜色处的信息,显然是 O ( n 2 log ⁡ n ) \rm O(n^2\log n) O(n2logn) 不可过的。

我们不妨把颜色分为两类:存在一部分在左端点前的,和全部都已在左端点及以后的。

对于后者,我们可以对其进行恰好一次操作,把每个位置对答案的贡献加进线段树里,这样显然均摊是 O ( log ⁡ n ) \rm O(\log n) O(logn) 的。

对于前者,我们可以发现,由于上面提到的线段不相交的结论,这些颜色一定是不交错的若干段:
在这里插入图片描述
那么对于在查询点前的一部分,显然可以每次只将颜色的最靠后位置的贡献更新并加入线段树。对于恰好在查询位置的颜色,因为只有一种,我们单独找出来算最长距离即可。

最后将两部分的答案取最优即可。由于查询比修改多,所以建议把单点修改、区间查询改为区间修改、单点查询。不必用 set,两部分的修改查找都可以用仅保留懒标记的 zkw 线段树进行小常数实现。总复杂度 O ( ( n + m ) log ⁡ n ) \rm O((n+m)\log n) O((n+m)logn)

代码

主要是离线很慢

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=100005;
const ll INF=1e17;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

int n,m,fa[MAXN],cl[MAXN];
char in[MAXN];
int a[MAXN],sk[MAXN],pr[MAXN],tl[MAXN],le;
int fr[MAXN];
#define pii pair<int,int>
#define fi first
#define se second
int f[MAXN<<2],g[MAXN<<2],p;
int L[MAXN*40],R[MAXN*40],to[MAXN*40],ans[MAXN*40],G[MAXN];
inline void init(int n){
	for(p=1;p<n+2;p<<=1);
	for(int i=p+n+1;i;i--)g[i]=n+1;
}
inline void addf(int l,int r,int d){
	for(l=p+l-1,r=p+r+1;l^1^r;l>>=1,r>>=1){
		if(~l&1)f[l^1]=max(f[l^1],d);
		if(r&1)f[r^1]=max(f[r^1],d);
	}
}
inline void addg(int l,int r,int d){
	for(l=p+l-1,r=p+r+1;l^1^r;l>>=1,r>>=1){
		if(~l&1)g[l^1]=min(g[l^1],d);
		if(r&1)g[r^1]=min(g[r^1],d);
	}
}
inline int query(int x){
	int res=0,y;
	for(y=p+x;y;y>>=1)res=max(res,f[y]);
	y=n+1;
	for(x=p+x;x;x>>=1)y=min(y,g[x]);
	return max(res,y-fr[fa[y]]);
}
signed main()
{
	freopen("grocery.in","r",stdin);
	freopen("grocery.out","w",stdout);
	n=read(),m=read();
	init(n);
	scanf("%s",in+1);
	sk[++le]=0,fa[n+1]=n+1,fr[n+1]=n+1;
	for(int i=1;i<=n;i++){
		a[i]=a[i-1]+(in[i]=='F'?1:-1);
		while(le>0&&a[sk[le]]>a[i])le--;
		if(le>0&&a[sk[le]]==a[i])fa[i]=fa[sk[le]],pr[i]=sk[le];
		else fa[i]=i,pr[i]=-1;
		sk[++le]=i;
	}
	for(int i=1;i<=m;i++)
		L[i]=read()-1,R[i]=read(),to[i]=G[L[i]],G[L[i]]=i;
	for(int i=n;i>=0;i--){
		if(fr[fa[i]]>i)addg(i,fr[fa[i]]-1,i);
		fr[fa[i]]=i,tl[fa[i]]=max(tl[fa[i]],i);
		addf(tl[fa[i]],n,tl[fa[i]]-i);
		if(i==fa[i])for(int j=tl[i];j>i;j=pr[j])addf(j,n,j-i);
		for(int j=G[i];j;j=to[j])ans[j]=query(R[j]);
	}
	for(int i=1;i<=m;i++)print(ans[i]);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值