单调队列优化
在写斜率优化之前,我们来回顾一下单调队列优化的dp
1. 对于如下形式的dp方程
dp[i]=min{dp[j]+f(j)}(0<j<i)
我们直接用一个变量维护(0, i)中dp[j] + f(j)的最小值即可
2.对于如下形式的dp方程
dp[i]=min{dp[j]+f(j)}(i?m<j<i)
我们可以用一个单调队列维护一个(i - m, j)中dp[j] + f(j)的最小值,然后做到O(1)转移。
斜率优化
基本形式
但是对于形如
dp[i]=min{dp[j]+f(i,j)}
的方程,无法做到O(1)计算dp[j]+f(i,j)的最小值,这时就需要斜率优化这个技巧来解决这个问题了。
令k < j < i,当我们更新dp[i]时,如果有dp[j] + f(i, j) 比dp[k] + f(i, k)更优,则有dp[j] + f(i, j) - (dp[k] + f(i, k) < 0,对于这个不等式如果能够化解成如下形式
Y(j)?Y(k)X(j)?X(k)<f(i)
我们就能通过斜率优化这个dp了。
让我们来举一个例子: hdu3507 dp方程为
dp[i]=min{dp[j]+M+(sum[i]?sum[j])2(0<j<i)}
令k < j < i,当有
dp[j]+M+(sum[i]?sum[j])2?(dp[k]+M+(sum[i]?sum[k])2)<0
从j转移到i, 比从k转移到i更优,变换此不等式可得:
(dp[j]+sum[j]2)?(dp[k]+sum[k]2)sum[j]?sum[k]<2sum[i]
令Y(i)=dp[i]+sum[i]2, X(i)=sum[i], f(i)=2sum[i]则将此不等式化解为上述形式。
优化方法:
可以发现,若满足Y(j)?Y(k)X(j)?X(j)<f(i)则j转移到i,比k转移到i更优,如果我们把(X(j), Y(j)), (X(k), Y(k))当成平面上的两个点Pj, Pk,这个不等式的含义即为若PjPk?→??的斜率<f(i)则,从j转移更优。
令grad(i, j)表示PiPj?→??的斜率,现在我们假设grad(i,j) < grad(j, k),若grad(i, j) < f(I),则i比j更优,若grad(i, j) > f(I), 则grad(j, k) > f(I),那么从k转移比从j转移更优,当grad(i, j) < grad(j, k)的时候,无论如何j转移到i都不会是最优。而这种情况恰好对应下图
所以这种情况时,我们可以直接把j点删除,最后能够转移的点集只会存在这种图形,
所以最后我们维护一个上凸集即可。
但是此时我们还是没有解决最终问题,如何才能找到转移到i点的最优的点呢。可以发现最后的点集一定是一个凸集,也就是斜率单调!!这样对于k < j, grad(j,k) < f(i),时更优,从图形特点我们可以发现如果j比k优,那么j点比所有比k小的点都优,所以对于每一个f(i),我们维护一个所有比i点小的凸集,二分查找斜率比f(i)小的编号最大的点,就是最优的转移点。如果f(i)也满足单调性,比如这道题,我们还可以直接维护一个单调队列就能解决这个问题。
分治做法
对于f(i)单调的这种情况,除了使用单调队列优化的斜率优化做,我们还有另外一种分治的做法,但是复杂度会变成O(nlogn) 比O(n)差。
当f(i)单调的时候,我们可以发现若a > b,则f(a) > f(b),设转移到a的最优点是c,转移到b的最优点是d,一定有c > d。也就是转移到a的最优点一定大于等于转移到b的最优点。考虑这样的分治
void dfs(int l, int r, int dl, int dr) {
//[l,r]表示现在更新[l,r]区间dp[i]的最优值
//用j -> f(i),表示j是更新f(i)最优值的最优点
//那么[dl,dr]表示更新dp([l,r])的点,一定在[dl,dr]范围内
int mid = (l + r) >> 1;
int dm = dl;
int g = inf;
for (int i = dl; i <= dr; i++) {
if(g < dp[i] + f(i, mid)) {
g = dp[i] + f(i, mid);//记录更新dp[mid]的最优
dm = i;//记录更新dp[mid]的最优点
}
}
dp[mid] = g; //更新dp[mid]的值
//因为上文叙述的单调性,
//更新[l,mid-1]的最优点,一定在[dl,dm]范围内
if(l < mid) dfs(l, mid - 1, dl, dm);
//更新[mid+1,r]的最优点,一定在[dm,dr]范围内
if(mid < r) dfs(mid + 1, r, dm, dr);
}
可以发现这个分治比起斜率优化,不仅写起来方便很多,并且适用的范围也更广。这个做法不局限于斜率单调,可以发现只要满足c是更新f(a)的最优点,d是更新f(b)的最优点,若a > b 一定有 c > d,则可以有这个分治做。
PS:
这个做法是我在codeforces 674E,跟Claris神犇的代码学会的solution,在此特地感谢Claris.这个做法着实是非常的劲啊!多一个log,但是换来编码复杂度和通用性更广的解法。