Codeforces 834D

原题链接

题意

给你一个长度为n的序列,要求将其分成k段,每一段的贡献是这一段中不同的数的个数,求最大总贡献。

\(1\leq n \leq 35000,1\leq k \leq min(n,50)\)

题解

状态:\(f(i)(j)\)表示前\(i\)个数分成\(j\)段的最大贡献

转移:\(f(i)(j) = max_{k=0}^{i-1}f(k)(j-1)+w(k+1,i)\)

时间复杂度:\(O(n^2k)\)

观察转移方程,不难发现\(f(i)(j)\)的决策集合与\(f(i-1)(j)\)决策集合有重合性;且附加值呈现区间性变化:所有\(w(k',i)\)\(w(k',i-1)\)多1,其中\(k'\)\(i\)的前一个同类元素\(pre(i)\)之后;其余\(w\)不变。

区间增加,最值查询,不妨考虑数据结构维护。

\(f(i = 1 \rightarrow n )(j-1)\)全部载入线段树中。处理\(f(i)(j)\)时,在线段树中将\([pre(i) , i - 1]\)区间加1,再对\([0,i-1]\)\(max\)即可。

时间复杂度:\(O(nlognk)\)。[代码见此](https://github.com/littlewyy/OI/blob/master/cf 834D segment tree.cpp)

另解

根据决策集合的重合性和附加值仅末尾一段增加的性质,不难得出一个结论:

\(f(i)(j)\)相对于\(f(i-1)(j)\)的最优决策点单调不左移。

当dp具有决策单调性时,可以使用分治法求解。

具体地,计算完\(f(1\rightarrow n)(j-1)\)后,整体地转移到\(f(1 \rightarrow n)(j)\)。对于\(f(l \rightarrow r)(j)\)的求解,令$mid = (l + r) /2 \(,暴力求出\)f(mid)(j)\(及其最优决策点\)dmid\(,则可确定\)f(l\rightarrow mid)(j)\(的决策点在\)dmid\(及其左边,\)f(mid +1 \rightarrow r)(j)\(的决策点在\)dmid$及其右边,递归下去求解即可。

分治至多\(logn\)层,每层时间复杂度\(O(n)\)。共进行\(k\)次分治求解。

时间复杂度:\(O(nlognk)\)。[代码见此](https://github.com/littlewyy/OI/blob/master/cf 834D monotony.cpp)

实现上的细节问题:注意分治的意义在于决策区间被限定在\([ll,lr]\),递归函数内注意循环的范围。另外,快速求解\([l,r]\)中不同数的种类数,可以使用差分+可持久化线段树。若每次计算\(w\)都调用线段树,复杂度会多一个log;实际上只需调用1次,计算出\([lr+2,mid]\)中不同数的个数,其余在从后往前枚举决策点时累加即可。

posted @ 2019-08-04 14:25  littlewyy  阅读(225)  评论(0编辑  收藏  举报