最近发现HDU上的题目ms比POJ上的好些似的, 因为每次都是一道题搞一天。 也说明我真的还是很菜啊。 昨天搞了一道题,一道最短路的题目,弄了一天。其实最主要的原因是没有搞清楚Dijkstra、SPFA、Bellman_ford这几个最短路算法的复杂度。这里作个总结,数组实现的Dijkstra,复杂度为O(N^2);用优先队列优化的Dijkstra复杂度为O(ElogE),但是图需要用邻接矩阵实现;Bellman_ford,O(VE);SPFA,O(KE),K<
今天做了一道DP的题目,题意如下:
【题目大意】
有N个数,现要将它们分成连续的若干段,每段的代价为(∑Ci)^2+M,求最小的代价。
【题目分析】
容易得到这样的一个动态规划算法:
令dp[i]表示前i个数分成若干段的最小代价,能得到一个经典的动态转移方程:
dp[i]= Min (dp[j] + Cost(j+1,i))+M,0<=j。
其中Cost(a,b)表示把a..b这些数分成一段的费用。
这个算法的时间复杂度是O(N^2)。 这题N有500000,必须优化。
我们看到Cost(j+1,i)。如果预处理出1..i的和,记为s[i],那么Cost可以表示为(s[i]-s[j])^2。
将它展开并且代入转移方程中可以得到:dp[i]= Min (dp[j] + s[i]^2 -2s[i]*s[j] +s[j]^2)+M。
因为s[i]^2是只和i有关的,可以移到Min的外面,得到
dp[i]= Min (dp[j] -2*s[i]*s[j] +s[j]^2) + M + s[i]^2。
现在我们单独考虑Min()里面的内容。
如果我们设k=2*s[i],x=s[j],y=dp[j]+s[j]^2的话(注意将和i有关的量以及和j有关的量结合到一起)
再令G=dp[i],那么将得到:
G=-k*x+y。
移项得:
y=k*x+G。
看到这个式子,知道斜率优化的朋友显然可以做出来了。
这里我讲讲何谓斜率优化。
得到这个式子之后,我们可以看到,k是一个常数(由当前枚举的i在O(1)时间内计算得出)。将这个式子看成是一个直线的函数表达式的话,k就是斜率,也就是说这是一个斜率固定的直线。
y和x则是和j有关的常量。而j的这些值应该都已经在之前计算过了。(因为j)
这个式子中,G是未知的,G和y以及x有关。
如果我们把每个j对应的x和y值看成一个坐标系中的点的话。
那么当我们枚举到i时,坐标系中就有一系列的点。
对于每个点,做一条斜率为k的直线,就能得到一个G的值。G的值为这条直线与y轴交点的纵坐标。
我们可以看到,实际上,如果我们让G的值由负无穷变化到正无穷,相当于一条直线,它满足斜率为k,然后从坐标系的下方慢慢地向上平移到坐标系的上方。
那么,我们要找到,G的最小值,就是在这个过程中,这条直线所碰到的第一个点!
而这个点,必然是这个点集的凸包上的点。(不知道凸包概念的去baidu一下好了)
再加上很关键的一点。随着i的增加,点的坐标是单调不下降的(s[i]),直线的斜率也是单调不降的(2s[i])!
满足这两个单调性,我们就可以利用单调队列,来维护一个凸壳。因为我们要找G的最小值,所以要维护一个下凸壳。方法和之前那篇数形结合题一样,类似Garham求凸包的算法。
之所以可以用单调队列是因为坐标和斜率都满足单调性的话,可以证明每个点如果不是某个i的最优决策,也不能会是之后的i的最优决策,可以被抛弃。
因为每个i只会进出队列一次,所以时间复杂度降为O(N),只需要保存单列就可以,空间复杂度为O(N),比较完美地解决了这个问题。