题解 CF1149C 【Tree Generator™】
KaisuoShutong · · 题解
码长文不易,点个赞吧。QwQ
UPD:修复部分错误
题目概述
中文翻译很清楚了。不过有一点是没有讲清楚的:关于如何使用树构建括号序列。
Dfs,每经过一条边,往下走就加入“(”,往上走就加入“)”。
题目分析
™好评
首先我们来证明一个引理。
引理1.1 若从序列中任取一段连续子序列,从中去掉所有匹配括号后,剩下的括号组成的路径一定为一条链,链长为剩下的子序列长。
以下是一个例子。
对于括号序列(()(())),其构成的树如下:
其中,若我们取:后六个括号(中文括号没法标红好烦啊)
其对应的即为从节点3到节点1的链。
我们可以发现,2-4-5-4-2的链在括号序列上因为匹配而被消掉了。
因此,括号匹配即表示这条链下去过,但又上来了,因此对答案没有贡献。
这是一个似曾相识的思想,可是我记不起是什么了\笑
这样,第二个引理就出现了。
引理1.2 树上直径长度即为任意区间去掉匹配括号后的长度的最大值。
考虑直径的构成情况。它只会有两种。
图有点龊,将就看吧。
第二种没有问题,但是第一种。注意到它也是可以被括号序列表示出来的。比如()(()())选第5、6个括号。其实这点在刚刚的例子中已经体现出来了。
这时候我们给出第三个引理。
若我们给”(”赋值 +1,给”)”赋值 -1,则:
引理1.3 最长去匹配区间 = 最大的(将区间分成两段)后面的权值和 - 前面的权值和
这是为什么呢?我想你可能会有以下疑问:
Q:如果我把一个括号在分配时分成两半了怎么办啊?
A:依赖于负数权值,不会有任何影响。因为此时的值为 -1-(+1)=-2,对比之前的 -1+1=0,你只会选择之前的办法,根本不会考虑这种毫无用处的办法。
Q:对于)()())这种序列岂不是无法划分?
A:可以的,只需要全部划分到前半部分即可。
Q:(略加思考后)那你证明一下吧。
A:因为匹配的括号在灵魂拷问三连第一问中已经证明了是无用的(即贡献为0),所以整个序列可以被简化为以下三种:
1> ……((((((……
2> ……))))))……
3> ……)))(((……
对于第一种和第二种我们只需将中间位置移到左边和右边即可。而对于第三种,放到中间,所以后减前的好处就体现出来了——两边相减恰为答案。
那么,思考:如何维护相邻区间差最大值呢?
用线段树(废话)。
不妨设这个最长去匹配区间为Δ。
我们选择维护九个变量:区间和,左/右连续选值最大/小值,从左/右连续选值Δ最大值,选择整个区间Δ最大值,无限制连续选值Δ最大值(最终答案)。
接下来考虑如何向上更新。
区间和:直接加。
左/右连续选值最大值,无限制连续选值最大值:Can you answer these queries III
从左/右连续选值Δ最大值:以左为例,先给转移式:
这个真的画个图看一下就明白了。用到的思想就是,枚举中间点分别在左区间和右区间的情况,取max。
选择整个区间Δ最大值:同上一个的思想:
无限制连续选值Δ最大值:同上一个的思想:
这题就做完了。掐头去尾后40行都没有。
void Pushup(int k)
{
s[k]=s[k<<1]+s[k<<1|1];
lmx[k]=max(lmx[k<<1],s[k<<1]+lmx[k<<1|1]);
rmx[k]=max(rmx[k<<1|1],s[k<<1|1]+rmx[k<<1]);
lmn[k]=min(lmn[k<<1],s[k<<1]+lmn[k<<1|1]);
rmn[k]=min(rmn[k<<1|1],s[k<<1|1]+rmn[k<<1]);
lv[k]=max(max(lv[k<<1],lv[k<<1|1]-s[k<<1]),mv[k<<1]+lmx[k<<1|1]);
rv[k]=max(max(rv[k<<1|1],s[k<<1|1]+rv[k<<1]),mv[k<<1|1]-rmn[k<<1]);
mv[k]=max(mv[k<<1]+s[k<<1|1],mv[k<<1|1]-s[k<<1]);
mm[k]=max(max(max(mm[k<<1],mm[k<<1|1]),lv[k<<1|1]-rmn[k<<1]),rv[k<<1]+lmx[k<<1|1]);
}
char S[maxn];
void Build(int k,int l,int r)
{
if(l==r) return s[k]=(S[l]=='('?1:-1),lmx[k]=rmx[k]=max(s[k],0),lmn[k]=rmn[k]=min(s[k],0),lv[k]=rv[k]=mv[k]=mm[k]=1,void(0);
int mid=l+r>>1;
Build(k<<1,l,mid),Build(k<<1|1,mid+1,r);
Pushup(k);
}
void Update(int k,int l,int r,int p,char d)
{
if(l==r) return s[k]=(d=='('?1:-1),lmx[k]=rmx[k]=max(s[k],0),lmn[k]=rmn[k]=min(s[k],0),lv[k]=rv[k]=mv[k]=mm[k]=1,void(0);
int mid=l+r>>1;
if(p>mid) Update(k<<1|1,mid+1,r,p,d);
else Update(k<<1,l,mid,p,d);
Pushup(k);
}
char SWAP(char&x,char&y) {char z=y;y=x,x=z;}
signed main()
{
int x,y;
n=read()-1<<1,m=read(),rein(S+1);
Build(1,1,n),printf("%d\n",mm[1]);
while(m--) x=read(),y=read(),S[x]^S[y]?(SWAP(S[x],S[y]),Update(1,1,n,x,S[x]),Update(1,1,n,y,S[y])):void(0),printf("%d\n",mm[1]);
return 0;
}