CF380C Sereja and Brackets 题解

题目大意

维护一段长度 \(|s|\leq 10^6\) 的括号串,需要回答 \(m\leq 10^5\) 次询问,每次询问给定区间 \([l,r]\),求 \([l,r]\)子序列中最长的合法括号串长度。

分析

考虑一个括号串的子序列中最长的合法括号串长度,不妨分别计算其中没有匹配的左括号和右括号个数。

不妨分治计算其中左半子串与右半子串中没有匹配的左括号个数和右括号个数。

显然左半子串中没有匹配的左括号可以和右半子串中没有匹配的右括号进行匹配。进行合并时减掉即可。

由于是对于一个括号串的子串进行询问,自然而然地可以想到 Segment Tree。两个区间合并的过程实际上即 pushup 的过程。

如果说的不够清楚,可以用式子表示一下。下标 \(l\) 表示未匹配的左括号,\(r\) 表示未匹配的右括号。

\[\begin{cases}\text{tot}_l=\text{left son}_l+\text{right son}_l-\min(\text{left son}_l,\text{right son}_r)\\\text{tot}_r=\text{left son}_r+\text{right son}_r-\min(\text{left son}_l,\text{right son}_r)\end{cases} \]

代码

#include<bits/stdc++.h>
#define HohleFeuerwerke using namespace std
#pragma GCC optimize(3,"Ofast","-funroll-loops","-fdelete-null-pointer-checks")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#define int long long
HohleFeuerwerke;
inline int read(){
	int s=0,f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
	for(;isdigit(c);c=getchar()) s=s*10+c-'0';
	return s*f;
}
inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>=10) write(x/10);
	putchar('0'+x%10);
}
const int MAXN=1e6+5;
int n,T;
char str[MAXN];
struct node{
	int l,r;
	int lsum,rsum;
}t[MAXN*4];
inline void update(int i){
	t[i].lsum=t[i*2].lsum+t[i*2+1].lsum-min(t[i*2].lsum,t[i*2+1].rsum);
	t[i].rsum=t[i*2].rsum+t[i*2+1].rsum-min(t[i*2].lsum,t[i*2+1].rsum);
	//由于左右区间合并时不能保证每一个左半区间没有匹配的左括号都对应右边没有匹配的右括号,做减法时取的是较小值。
}
inline void build(int i,int l,int r){
	t[i].l=l,t[i].r=r;
	if(l==r){
		if(str[l]=='(') t[i].lsum=1;
		if(str[l]==')') t[i].rsum=1;
		return;
	}
	int mid=(l+r)/2;
	build(i*2,l,mid); build(i*2+1,mid+1,r);
	update(i);
}
inline node query(int i,int l,int r){
	if(t[i].l>=l&&t[i].r<=r){
		return t[i];
	}
	int mid=(t[i].l+t[i].r)/2;
	if(l>mid) return query(i*2+1,l,r);//待查区间完全在 mid 右侧
	if(r<=mid) return query(i*2,l,r);//待查区间完全在 mid 左侧
	//注意这里和普通线段树只需要有交集就查的区别
	//如果待查区间在两边内都有
	node tl=query(i*2,l,r),tr=query(i*2+1,l,r);
	node ans;
	ans.lsum=tl.lsum+tr.lsum-min(tl.lsum,tr.rsum);
	ans.rsum=tl.rsum+tr.rsum-min(tl.lsum,tr.rsum);
	//实际上即合并左右子区间
	return ans;
}
signed main()
{
	scanf("%s",str+1);
	n=strlen(str+1);
	build(1,1,n);
	T=read();
	while(T--){
		int l=read(),r=read();
		node tmp=query(1,l,r);
		write((r-l+1)-tmp.lsum-tmp.rsum);
		//所求的是匹配的子序列长度。可以用总长-没有匹配的左括号个数-没有匹配的右括号个数来计算 
		puts("");
	}
	return 0;
}
posted @ 2021-10-04 22:19  _HofFen  阅读(74)  评论(0编辑  收藏  举报