P3976
前置芝士:树链剖分、线段树。
首先树剖套线段树是显然的因为标签这么写。
线段树维护什么?
如果只维护区间最小值和区间最大值似乎不足以解决问题。
题目要求区间最值之差,而且可以发现,对于同一个区间,从左到右和从右到左所得到的答案是不同的,所以要按方向不同分开维护。
所以线段树维护四个值:
1.区间最大值。
2.区间最小值。
3.从左至右的最值之差。
4.从右至左的最值之差。
需要什么标记?
因为只有区间加一种操作,所以只需一个加法懒标记即可。
如何合并和维护?
-
区间最大值和区间最小值的维护和合并就是板子,不再赘述。区间加的操作也一样。
-
从左至右的最值之差的合并:如果不跨区间,就是两个子节点各自的从左至右的最值之差的最大值;如果跨区间,就是左儿子的最小值和右儿子的最大值之差。
-
从右至左的最值之差的合并:如果不跨区间,就是两个子节点各自的从右至左的最值之差的最大值;如果跨区间,就是右儿子的最小值和左儿子的最大值之差。
合并部分的代码:
tree merge(tree x,tree y)
{
tree t;
t.mi=min(x.mi,y.mi);
t.ma=max(x.ma,y.ma);//最大值和最小值的合并
t.lm=max(x.ma-y.mi,max(x.lm,y.lm));
t.rm=max(y.ma-x.mi,max(x.rm,y.rm));//最值之差的合并要分三种情况
return t;
}
树剖如何操作?
与染色这道题类似,用两个结构体变量存储当前已经跳过的部分的信息。
因为在树上,“左”、‘右’的划分应变为时间戳的大小,而跳链的操作是从下往上,即时间戳从大到小,所以结构体中存储的信息是在“右边”的,在合并的过程中要注意这一点。
求值操作的代码:
void c_q(int u,int v)
{
tree L,R;L.mi=R.mi=INF;//存储信息的结构体
while(t[u]!=t[v])
if(d[t[u]]<d[t[v]])
{
R=merge(query(1,id[t[v]],id[v]),R);//合并过程中,结构体在右
v=fa[t[v]];
}
else
{
L=merge(query(1,id[t[u]],id[u]),L);
u=fa[t[u]];
}
if(d[u]>d[v])L=merge(query(1,id[v],id[u]),L);
else R=merge(query(1,id[u],id[v]),R);
swap(L.lm,L.rm);//合并两个结构体的信息,其中L的左右最值差要交换
cout<<merge(L,R).rm<<endl;
}