SYZOJ 20220218 E 合法括号串 & CF380C Sereja and Brackets
written on 2022-02-22
这题很有意思,因为我以前从来没有用这种写法写过题。
题目要求的是区间内可匹配的括号数。
直接思路是用栈在区间内模拟,加上切分操作,亲测可拿 \(70pts\) 。
正解是用 线段树 维护,维护什么呢?
注意到题目要求的是区间内可匹配的括号数,直接求肯定有难度,因此在看了题解后我发现还要维护两个值, 该区间内未匹配的左括号数以及右括号数 。
重点来看线段树的 \(pushup\) (第二次颠覆了我对线段树只能进行区间求和以及区间查询的认知(第一次是 \(lost cows\) ))。
先上代码:
void push_up(int p)
{
int k=min(vall(p<<1),valr(p<<1|1));
now(p)=now(p<<1)+now(p<<1|1)+k;//now(p) 区间内可匹配的括号组数
vall(p)=vall(p<<1)+vall(p<<1|1)-k;//vall(p) 区间内不能匹配的左括号数
valr(p)=valr(p<<1)+valr(p<<1|1)-k;//valr(p) 区间内不能匹配的右括号数
}
对于 \(now\) ,显然是左右两个子节点 可以匹配的 \(now\) 之和 再加上 左边未匹配的左括号 与 右边未匹配右括号 的最小值(即为 \(k\) )。
对于 \(vall\) ,它的值是由 左子节点和右子节点的 \(vall\) 之和减去新匹配的括号数(即为\(k\))。
对于 \(valr\) ,它的值是由 左子节点和右子节点的 \(valr\) 之和减去新匹配的括号数(即为\(k\))。
最后要注意在递归询问时也动态更新(但不需赋值)。
code
#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n;
char a[N];
struct f
{
int l,r,now,vall,valr;
#define l(p) t[p].l
#define r(p) t[p].r
#define vall(p) t[p].vall
#define valr(p) t[p].valr
#define now(p) t[p].now
}t[N<<2];
void push_up(int p)
{
int k=min(vall(p<<1),valr(p<<1|1));
now(p)=now(p<<1)+now(p<<1|1)+k;
vall(p)=vall(p<<1)+vall(p<<1|1)-k;
valr(p)=valr(p<<1)+valr(p<<1|1)-k;
}
void build(int p,int l,int r)
{
l(p)=l,r(p)=r;
if(l==r)
{
if(a[l]=='(') vall(p)=1;
else if(a[l]==')') valr(p)=1;
return ;
}
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
push_up(p);
}
f ask(int p,int l,int r)
{
if(l<=l(p)&&r>=r(p)) return t[p];
f a=(f){0,0,0,0,0},b=(f){0,0,0,0,0};
if(l<=r(p<<1)) a=ask(p<<1,l,r);
if(r>=l(p<<1|1)) b=ask(p<<1|1,l,r);
int k=min(a.vall,b.valr);
return (f){0,0,a.now+b.now+k,a.vall+b.vall-k,a.valr+b.valr-k};
}
int main()
{
scanf("%s",a+1);
n=strlen(a+1);
build(1,1,n);
int q;
scanf("%d",&q);
while(q--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",ask(1,l,r).now*2);
}
}