算法 | 青训营

52 阅读3分钟

2023HDU多校9__Capoo on tree(二分+树链剖分+可持久化线段树)

题目链接

Solution

Hint1Hint1 考虑如何进行对某一相同点权的所有点进行点权+1+1操作,如果我们建立权值线段树,那只需要将权值为xx的点并入权值x+1x+1即可,但是这样就算我们建立以节点编号为版本的可持久化线段树,也是无法处理询问的,因为题目的询问是问一条路径上的一个连续子段,那想要完成这个询问就只能枚举路径上每一个节点,去查看每一段长为ll的路径中是否只出现了y\ge y的权值,复杂度是不允许的,并且想要找到这条路径的另一个端点也很困难。那么我们不妨试试以权值建立版本,那一般来说我们会从小到大建立版本,那么此时的版本为valval的线段树存储的就是所有val\le val的节点信息,由于询问的是y\ge y的信息,所以我们直接从大到小建立版本,省得再做一次差分,这样子我们的版本存储的就是所有val\ge val的节点信息。那么此时我们的+1+1操作就相当于把版本为xx的信息替换x+1x+1,那么同理1-1操作就相当于把x+1x+1的版本信息替换xx,由于是路径上的操作,所以要树剖后进行区间更新,把对应的区间版本进行替换就行。

Hint2Hint2 考虑如何求答案,由于我们现在有了y\ge y的节点信息,我们考虑维护什么样的信息能够得到uu和路径上最早出现连续一段y\ge y且长度至少为ll的子段之间的距离,如果我们能够找到起点,那直接求一下lcalca就可以得到距离,那经典的题目有维护最大子段和,我们可以类似的通过一个前缀和后缀以及长度来维护出一个长度至少为ll的最大子段。信息按照如下进行合并即可

Info operator+(const Info &x, const Info &y) {
    Info res;
    res.pre = (x.pre == x.len ? x.pre + y.pre : x.pre);
    res.suf = (y.suf == y.len ? x.suf + y.suf : y.suf);
    res.Max = max({x.Max, y.Max, x.suf + y.pre});
    res.len = x.len + y.len;
    return res;
}

通过给出的一条路径我们会得到若干条链,我们考虑把这些链全都处理出来,那只要按照经典的树链剖分去做就好了,但是我们需要的路径是从ulcau\rightarrow lca的路径和从lcaulca \rightarrow u, 上面的dfndfn序可能并不单调递增或递减的,需要讨论清楚(具体参考代码)。我们处理出这些链之后我们遍历这几条链来进行处理询问,有两种情况。一种是前面链的后缀和当前链的前缀进行拼接,还有一种是在某一条链的内部。对于第一种情况我们可以直接利用dfndfn的连续性做差得到目标点,第二种情况我的做法是二分终点再做差得到目标点,可以把二分放到线段树上但我不会QAQ。

复杂度O(nlog2n)O(n\log^2{n})(极其简单的主函数,纯数据结构,除了版本处理的思维,剩下的都是对基本功的考验QAQ)

Code