点分治。
考虑经过当前分治中心(u)的点对数量。
这种数点对数的问题,有一个套路。我们可以依次考虑(u)的每个儿子,看用当前的儿子,能和之前已经考虑过的所有儿子,组成多少点对。这样所有合法的点对都会被恰好计算一次。
现在搜索(u)的一个儿子(v)的子树。对子树里的每个点,考虑它到(u)的有向路径形成的串。在搜索的过程中,我们每次要在当前串的“开头”处添加一个字符(即把整个串整体右移一位),没有什么好的数据结构可以维护,于是想到哈希。现在我们要判断,当前的串,是否是“若干个(s)”的一个前缀;如果是(称这样的节点是合法的),那我们要知道它最后匹配到的“零头”是多长,也即这个“前缀”的长度(mod m)的余数是多少。具体地,在搜索时,我们维护一个桶(buc)。(buc[i])表示有多少个合法的节点(x),使得(x)到(u)的串的长度(mod m=i)。
这样就维护出了所有的前缀。现在我们想知道,(v)的子树内每个合法的前缀,能匹配(v)之前的子树内的多少合法的后缀。在搜索时,我们用和维护前缀类似的方法来维护后缀。对后缀,我们把(s)整体反转,然后也开一个桶,做和匹配前缀时一样的操作即可。
同样,对于(v)子树内的所有合法的后缀,我们也要知道它能匹配(v)之前的子树内的多少合法的前缀。(这是因为路径是有向的,因此要拿(v)内的前缀匹配一次前面的后缀,再拿(v)内的后缀匹配一次前面的前缀)。
现在完成了对(v)的子树的搜索,也把(v)子树的贡献计入了答案。我们得到了两个桶,分别是(v)内所有合法前缀的串长(mod m)的值为(i)的点的数量,和(v)内所有合法后缀的串长(mod m)的值为(i)的点的数量。现在,(v)这棵子树的身份就从“当前子树”,变成了“当前子树之前的子树”。于是拿这两个(v)的桶去分别更新两个“全局桶”即可。
注意,桶的大小是(min(m, ext{maxdep}_v)),在更新全局桶和清空小桶时一定不能直接for
到(m),否则复杂度就不对了。
除了点分治,其他部分的复杂度是线性的。因此总时间复杂度(O(nlog n))。