\(\text{Description}\)
\(\text{Pre Cheese}\)
一棵树的括号序列表示了这棵树。左括号代表向下走,右括号是向上走。从根节点开始走。
一个和题目没什么关系的小结论:
一棵树的括号序列长度为 \(2\times (n-1)\)。
证明:每条边必定可以这样表示 \((......)\)(因为有去有回嘛)。所以一条边对应两个括号,边条数为 \(n-1\),括号序列长度则为 \(2\times (n-1)\)。
\(\text{Solution}\)
选取从 \(u\) 到 \(v\) 的括号序列(其实这个序列只要起点到 \(v\) 能经过 \(u\) 即可,但注意到了 \(v\) 就必须停止(设 \(u\) 在 \(v\) 遍历之前)),匹配的括号就消掉,剩下的括号形如 )))(((。则有:
剩余括号个数为 \(\text{dis}(u,v)\)。
证明:
考虑到 \(x\) 点时某对括号(假设这对括号代表的边能从上到达 \(y\),再回去)的匹配状态。
-
均已出现。\(y\) 不是 \(x\) 的祖先或子孙。
-
均未出现。\(y\) 不是 \(x\) 的祖先。
-
出现左括号。\(y\) 是 \(x\) 的祖先。
我们回来看 \(u,v\) 的括号序列。
- 能够匹配的括号。对于 \(u\) 是情况二,对于 \(v\) 是情况一,则 "既不是 \(u\) 的祖先,也不是 \(v\) 的的祖先或子孙"。根本不在路径上。
- 左括号。对于 \(u\) 是情况二,对于 \(v\) 是情况三,则 "不是 \(u\) 的祖先,但是是 \(v\) 的的祖先"。在路径上。
- 右括号。对于 \(u\) 是情况三,对于 \(v\) 是情况一,则 "是 \(u\) 的祖先,但不是 \(v\) 的的祖先或子孙"。在路径上(这时肯定不在 \(u,v\) 的公共祖先上)。
所以结论得证。
那么如何计算呢?
如果要消去匹配的括号,我们马上就会想到给括号赋值:( 为 \(1\),) 为 \(-1\)。但这样就会使 )))((( 的计算费些周折:考虑到剩余括号个数就是 \(3-(-3)=6\),我们的答案转化为求相邻两段区间和的差的最大值,可以用线段树维护。
接下来为了方便,设区间的两个左右区间为 \(l,r\)。
先思考我们要求的相邻两段区间和的差的最大值(令它为 \(ans\))该如何合并。首先有 \(ans_l,ans_r\) 的贡献,其次需要考虑跨区间的情况。我们对提供贡献的数据有这样的要求:
- \(l\) 选择的区间必须包含 \(\rm mid\),\(r\) 选择的区间必须包含 \(\text{mid}+1\)。
- 因为只能有一个分界点(即相邻两段区间的划分点),不能直接用 \(ans\) 来贡献,我们必须钦定分界点在 \(l/r\)。
由此就有了数组的定义:
- \(ans\):答案。
- \(sum\):区间和。
- \(lma\):最大前缀和。
- \(rmi\):最小后缀和。
- \(ld\):最大含分界点且包含左端点的答案。
- \(rd\):最大含分界点且包含右端点的答案。
- \(d\):最大含分界点且包含整个区间的答案。
如何维护相信大家都会了。我是真的不想写了。
另外需要特别注意的是一整个区间减去另一整个区间的情况。
\(\text{Code}\)
#include <cstdio>
#define rep(i,_l,_r) for(register signed i=(_l),_end=(_r);i<=_end;++i)
#define fep(i,_l,_r) for(register signed i=(_l),_end=(_r);i>=_end;--i)
#define erep(i,u) for(signed i=head[u],v=to[i];i;i=nxt[i],v=to[i])
#define efep(i,u) for(signed i=Head[u],v=to[i];i;i=nxt[i],v=to[i])
#define print(x,y) write(x),putchar(y)
template <class T> inline T read(const T sample) {
T x=0; int f=1; char s;
while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
return x*f;
}
template <class T> inline void write(const T x) {
if(x<0) return (void) (putchar('-'),write(-x));
if(x>9) write(x/10);
putchar(x%10^48);
}
template <class T> inline T Max(const T x,const T y) {if(x>y) return x; return y;}
template <class T> inline T Min(const T x,const T y) {if(x<y) return x; return y;}
template <class T> inline T fab(const T x) {return x>0?x:-x;}
template <class T> inline T gcd(const T x,const T y) {return y?gcd(y,x%y):x;}
template <class T> inline T lcm(const T x,const T y) {return x/gcd(x,y)*y;}
#include <iostream>
using namespace std;
const int maxn=2e5+5;
int n,m,a[maxn],ans[maxn<<2],sum[maxn<<2],lma[maxn<<2],rmi[maxn<<2],ld[maxn<<2],rd[maxn<<2],d[maxn<<2];
char str[maxn];
void pushUp(int o) {
int ls=(o<<1),rs=(o<<1|1);
sum[o]=sum[ls]+sum[rs];
lma[o]=Max(lma[ls],sum[ls]+lma[rs]);
rmi[o]=Min(rmi[rs],sum[rs]+rmi[ls]);
ld[o]=Max(ld[ls],Max(d[ls]+lma[rs],ld[rs]-sum[ls]));
rd[o]=Max(rd[rs],Max(d[rs]-rmi[ls],sum[rs]+rd[ls]));
d[o]=Max(d[ls]+sum[rs],d[rs]-sum[ls]);
ans[o]=Max(Max(ans[ls],ans[rs]),Max(ld[rs]-rmi[ls],rd[ls]+lma[rs]));
}
void Insert(int o,int l,int r,int pos,int k) {
if(l>pos||r<pos) return;
if(l==r) {
sum[o]=k;
lma[o]=Max(k,0),rmi[o]=Min(k,0);
ld[o]=rd[o]=ans[o]=d[o]=1;
return;
}
int mid=l+r>>1;
Insert(o<<1,l,mid,pos,k); Insert(o<<1|1,mid+1,r,pos,k);
pushUp(o);
}
int main() {
n=read(9)*2-2,m=read(9),scanf("%s",str+1);
rep(i,1,n) {
if(str[i]=='(') a[i]=1;
else a[i]=-1;
Insert(1,1,n,i,a[i]);
}
print(ans[1],'\n');
int x,y;
while(m--) {
x=read(9),y=read(9);
if(a[x]==a[y]) {print(ans[1],'\n'); continue;}
swap(a[x],a[y]);
Insert(1,1,n,x,a[x]); Insert(1,1,n,y,a[y]);
print(ans[1],'\n');
}
return 0;
}
浙公网安备 33010602011771号