【Wannafly挑战赛29F】最后之作(Trie树,动态规划,斜率优化)

【Wannafly挑战赛29F】最后之作(Trie树,动态规划,斜率优化)

题面

牛客

题解

首先考虑怎么计算\([l,r]\)这个子串的不同的串的个数。
如果\(l=1\),我们构建\(Trie\)树然后第\(i\)层的点的个数就是\([1,i]\)的答案。
如果\(l\)要向右移动一位,显然就是我们要把最上面那一层给删掉。
那么我们暴力对\(Trie\)树进行合并,因为每个点最多只会被合并一次,所以复杂度是\(O(|\sum|*tot)\)的,其中\(tot\)是\(Trie\)树点数,\(|\sum|\)是字符集大小。
因为数据范围较大,显然不可能暴力把所有\([l,r]\)的答案都维护出来,但是我们发现当\(l\)固定的时候,\(r\)单增时,\([l,r]\)不同的串的个数也是单增的,所以我们只需要维护一下单增的位置就行了。
考虑维护数组\(g[i][j]\)表示当前左端点在\(i\),不同的串的数量为\(j\)时最靠左的右端点。这个每次二分一下就行了。
接下来就把\(g[i][j]\)设为\([i,j]\)这段区间的不同的串的个数了,这个只需要在我们预处理的东西里二分。
如果\(dp\)的话,显然大家都会\(O(len^2)\)的暴力。
因为上面预处理是右侧的第一个右端点,所以我们考虑从后往前\(dp\)。如果要从前往后\(dp\)的话就维护以这个点为右端点的第一个左端点。这样子的话串要反着插入进\(Trie\)中。
设\(f[i]\)表示当前已经考虑完了\([i,len]\)的最大贡献,显然\(i\)是左端点,我们就需要枚举一个右端点,此时的转移是:
\[f[i]=\max_{r=i}^{len}\{f[r+1]+p*g[i][r]+a[i]*(r-l+1)+b[i]\}\]
我们对于\(g[i][r]\)相同的一段\(r\)可以一起考虑,这样子要求的本质上就是\(val[r]+a[i]*r\)的最大值,其中\(val[r]\)是只和\(r\)相关的东西。发现这个东西是一个一次函数的形式,要求在\(x=a[i]\)位置时的最大值。不难想到这是一个斜率优化的形式,于是就可以拿李超线段树直接维护了,这样子的复杂度是\(O(n*len*logn)\)。

首先注意到因为\(p>0\),所以即使我们可以直接把\(g[i][r]\)更大的\(r\)也给当成当前的\(g[i][r]\),也就是我们每次转移都是从一段后缀转移过来。
于是我们的问题可以变成从后往前依次把点加入进凸壳,在每个时刻凸壳加入完之后,我们要把它给转移到若干位置上去。这样子发现逃脱不了在凸壳上二分的问题。也就是我们并不能优化复杂度。因为\(a_i\)不具有单调性。
那么,现在我们把\(dp\)正过来做,变成:
\[f[i]=\max_{l=1}^{i}\{f[l-1]+p*g[l][i]+a[l]*(i-l+1)+b[l]\}\]
于是要求的东西变成了\(val[l]+i*a[l]\)最大。
我们要构建的凸壳也变成了前缀凸壳,每次查询的\(x=i\)。
这个时候发现查询的东西具有了单调性,那么我们就可以有更加有趣的做法了。
考虑对于\(k\in[1,n]\)维护一个在凸壳上的指针,维护的表示的是\(g[l][i]=k\)时,在凸壳上查询的指针。每次修改凸壳的时候,如果当前指向的位置为删掉了,显然指针就会减一,然后再暴力向右移动就好了。
因为凸壳的点不单调,所以要用平衡树维护凸壳,简单的用\(set\)实现就好了。
这样子的复杂度是\(O(nm+nlogn)\)。

代码它咕咕咕了。

上一篇:讲解——Trie树(字典树)


下一篇:[转]SecureCRT右键粘贴的设置