CF1916E: Happy Life in University(HH的项链上树版)
这场E题就是HH项链上树版,VP时D看错两次题意(乐)导致E没时间看了。
D是说构造n个长度为n的完全平方数,而且n个数multiset要相同,一句话题解:1690000,9610000,1060900,9060100,1006009,9006001。
做E之前先来看看序列版:HH的项链。
HH的项链
茴香豆的茴有四种写法(无端联想)
经典的HH项链问题,不带修的区间颜色种类数,有好几种离线做法。
第一种就是莫队,板子题,但是HH项链那题数据范围太大过不去。
第二种是单点修,区间查。对于颜色种类数的题,大部分都是把种类数转化为区间和,每个点的贡献只存在于它到last这段区间。具体的,求出每个点前面和它颜色相同的位置last,对询问右端点排序,右部每加入一个新点,这个点的last就等同于没有贡献了,单点修,将新点+1,last点-1,区间求和就是颜色种类数。
第三种是区间修,单点查。这个方法就是第二个方法再转化一下,维护的是贡献的区间和而不是单点的贡献,发现区间和就是答案,所以相当于直接维护答案。
具体的,右端每加入一个新点 \(i\),将 \(i\) 到 \(last[i]\) 这个区间 +1,其实是 $[1,i] $ 加一, \([1,last[i]]\) 减一,询问 \([l,i]\) 的颜色种类数,线段树上 \(l\) 位置的单点就是答案。
由于修改的复杂度增加,因此这种方法在询问时方便进行扩展,比如问你 \(l\) 为多少时答案是最大值(根据贪心明显知道 \(l=1\) 是最大的,但这种询问上树之后就有用了),或者问你从x到y每个位置的l,对 \([l,r]\) 颜色数进行求和。这两个问题就转化成了区间修,然后区间求最值或求和。
CF1916E:Happy Life in University
先想一想这题的变种:不带修求树上某一条链的颜色种类数,很经典的树上莫队题,跟HH项链第一种解法大同小异。
但是因为任意路径的话缺少了“右端”的概念,所以第二种和第三种方法不适用。
再简单些:保证询问的链是祖宗到子孙的一条链。
这时候就可以用剩下的两种方法了。在树上,我们发现儿子可以分叉成好多个,而通往祖先的路只有一条,所以把通往祖先的方向类比为序列上的右端,询问任意一个子孙就是序列上的左端。
1. 单点修区间查:如果知道每个点对答案的贡献,那么利用链剖就可以求出某条链的贡献和,这是单点修区间查的思路。那么怎么进行单点修,我们得知道last,不同于序列的last,树上一个点的last可以有好多,这里的last定义为:与点 \(u\) 颜色相同的最浅子孙集合。
给个图就很好理解了,下图中1的last集为:2,4,5,因为把1加入后,2,4,5三个点就对答案没有贡献了,那6和8呢?它俩分别在2和4加入时就已经被删掉了贡献。
last集也非常好求,就反过来算出某个点离他最近的颜色相同的祖先,它就压进祖先的last集中就好了。
last集算完之后,对于新加入的祖先,我们单点修改就是把祖先的贡献加一,然后把它last集的位置减一。
2. 区间修单点查:我们单点修之后,发现包含这个点的所有区间答案都会改变,所以如果想维护每个点的答案,只需要修改这个点的子树即可。用dfs序,把子树修改转化成区间修改。查询时,问题就可以从单点查进行扩展:问你子树内或者一条链上所有点的答案和,或者问你子树内哪个点答案最大。诶,你发现后者不就是这道E题吗?
CF1916E Solution:先求出每个点last集合,然后从底向上遍历u,修就是把u的last子树区间减一,u的子树区间加一。更新答案:每个点u作为LCA时,查询它的每个儿子子树内最大值,选最大的两个儿子乘起来。
似乎很多题解都说可以贪心地选叶子,但其实没必要啊,因为我们所有点的答案都能算出来。
理解了原型就会各种变种了,我们称之为典题。所以经常训练的选手很多都是会做E却不会做D。。。
再变一变: 求子树内不同颜色个数?那也很简单,把树用dfs序拍扁到序列上就好了。