决策单调性优化

二分队列

反过来决策\(j\)所对应的\(i\)也是单调的。
\(lim(j)\)为决策为\(j\)的最小\(i\)
队列里存有意义的决策。
DP过程:

  • \(i\)的决策,弹队头。
  • 对尾弹掉不如\(i\)的决策,更新\(i\)的前驱的lim,把\(i\)插进去。
    方便可以写一个Search函数,查询两个决策对应状态集的分界点。

code:

点击查看代码
int Search(int x, int y) {
	int l = y + 1, r = n, bst = y;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(calc(mid, x) <= calc(mid, y)) {bst = mid; l = mid + 1;}
		else r = mid - 1;
	}
	return bst;
}
Q[hd = tl = 1] = 0;
for(int i = 1; i <= n; i++) {
	while(hd < tl && lim[hd] < i) hd++;
	f[i] = calc(i, Q[hd]), pos[i] = Q[hd];
	while(hd < tl && lim[tl - 1] > Search(Q[tl], i)) {tl--;}
	lim[tl] = Search(Q[tl], i); Q[++tl] = i;
}

\(w(i,j)\)的四边形不等式

  • 四边形不等式:满足交<并
  • 满足四边形不等式则满足决策单调性:\(pos(i-1,j) \le pos(i,j) \le pos(i+1,j)\)
    所以dp时\(pos(i,j)\)的决策从\([i,j]\)变为了\([pos(i-1,j),pos(i+1,j)]\)
    复杂度:\(O(n^2)\)。因为长度为\(L\)的由\(L-1\)更新,长度为\(L\)状态枚举决策点总复杂度为\(O(n)\)。因为\(pos(x,y)\in [pos(x-1,y),pos(x+1,y)]\),接着\(pos(x+1,y+1)\in [pos(x+1,y),pos(x+2,y+1)]\)...

斜率优化

限制要求还是挺多的
先剔除无关紧要的常数项。
柿子含三个部分,只跟\(i\)有关,只跟\(j\)有关,与\(i\),\(j\)都有关(如\(i*j\))。
此时把柿子化成一次函数(直线):\(y=k*x+b\)形式。

  • \(y\):只跟\(j\)有关

  • \(x\): \(i\)\(j\)骨肉相连中的\(j\)部分

  • \(k\):同上的\(i\)部分

  • \(b\):只跟\(i\)有关
    如目的是让\(f(i)\)最小,即直线的截距尽量小。
    发现决策是若干个已经确定的\((x,y)\)节点,然后斜率\(k\)也确定,即问题转化为了用固定斜率\(k\)的直线去切节点使得截距最小。
    画画图知道如果是截距最小队列维护下凸壳,否则上凸壳。
    然后凸壳上连线的斜率从左往右是单增的,而所切的点往左连的边斜率\(<k\),右连的斜率\(>k\),感觉像一个分界处把。

  • 必要前提是\(x\)单增,这样才可维护凸包。

  • 如果\(k\)单增可以单调队列\(O(n)\),否则就每次去二分啦,要带一个\(log\)
    code

点击查看代码
Q[hd = tl = 1] = 0; dp[0] = 0;
for(int i = 1; i <= n; i++) {
	while(hd < tl && Y(Q[hd + 1]) - Y(Q[hd]) <= K(i) * (X(Q[hd + 1]) - X(Q[hd]))) hd++;
	int j = Q[hd]; dp[i] = dp[j] + _pw(a[i] - a[j]) + m;
	while(hd < tl && (Y(i) - Y(Q[tl])) * (X(Q[tl]) - X(Q[tl - 1])) <= (Y(Q[tl]) - Y(Q[tl - 1])) * (X(i) - X(Q[tl]))) tl--;
	Q[++tl] = i;
}

ps.我跳过多次的坑:求斜率除法要考虑精度,因此比较大小同乘分母可以直接整数比较大小,但是要注意不等式同乘负数要变号。