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

完整的代码

posted @ 2022-02-07 17:06  AIskeleton  阅读(16)  评论(0编辑  收藏  举报