1. 程式人生 > 其它 >CF380C Sereja and Brackets 題解

CF380C Sereja and Brackets 題解

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;
}