[POJ3017]Cut the Sequence
给定a[]
,求一种划分方法,使得每段的和不大于M
,并且每段的最大值的和最小。
这题是好题,一年内我做了三遍,前两遍都是看题解,不断的改程序,最后碰运气过的。第三遍终于是自己做出来的。
前两次看到题目中最大值最小,总是会想到二分去,第三次一想,不对,如果能验证答案的话那也能直接求出答案。所以这题的正解还是动规。
f[i] = min{ f[j] + max{a[j+1]..a[i]} } (sum[i]-sum[j] <= M)
朴素方程是O(n^2)
的,下面考虑优化。
区间最大值,而且还有和的限制,显然RMQ
不管用了,然而单调队列正好就是处理这种事情的。保存一个单调递减的单调队列,那么我们取出来队头就是区间的最大值了。
假设我们队列里面的元素x
的位置和值分别是Q[x].pos
和Q[x].value
,那么对于当前的最优决策只能是:min{ f[Q[x-1].pos] + Q[x].value }
,因为从Q[x-1].pos
到i
这一段的最大值都是Q[x].value
。
如果要科学一点地解释的话,那就要证明:如果一个点k
能成为最优决策,当且仅当a[k] > max{a[k+1]..a[i]}
。原因也很简单,如果a[k]
不比后面的值大的话,那么我们不如在k
前面找一个x
,使得max{a[x+1]..a[i]} = max{a[k+1]..a[i]}
,又因为人类已经无法阻挡f[i]
递增的趋势了,注意看DP方程,会发现f[x] <= f[k]
,而后面那项又是相等的,所以k
肯定不如x
优。
下面我们该考虑如何得到最优决策,毕竟我们只是保证了队列里面的数是递减的,只是去掉了一些无用的决策,还是得对队列里面的每个元素计算 min{ f[Q[x-1].pos] + Q[x].value }
。容易发现,这个值插进队列里面以后是固定的(跟i
无关),所以随便拿一种O(log n)
的数据结构都可以维护这个的最大值。一般人回去打平衡树,我比较喜欢线段树,所以就用队列下标建了一棵线段树。另外堆也是可以的,如果用一个bool数组表示当前值是否被删,然后配合std::priority_queue<>
也是可以搞的(就跟Dijkstra + Heap
一样)。
程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|