决策单调性 dp 优化学习笔记

决策单调性 dp 学习笔记

对于决策单调性 dp ,是指的对于 \(dp_i\) 的最优决策点 \(f_i\) 。当 \(i<j\) 时,\(f_i<f_j\) 或者 \(f_i>f_j\)

如何证明呢?(其实直接打表就可以了)

这里令 \(dp_i=\max(dp_j+w(j,i))\)

要使 \(dp\) 有决策单调性 ,就要让 \(w\) 具有有四边形不等式(对于 \(i\le i'<j\le j'\) ,\(w(i,j)+w(i',j')\le w(i',j)+w(i,j')\))即可,其实比赛的时候不用真的去证明,只要看出来即可。

那我们已经看出来了决策单调性,如何去优化 dp 呢?

如果你已经发现了这题可以用单调队列或者斜率优化了,可以直接用。

但是如果不可以呢,这里提供 1D/1D 的两种方法。

先是比较简单的分治方法。这里需要决策点和被决策点独立。

这里取被决策点的中点,在决策区间中选出最佳决策点,因为决策单调性,所以在中点左边的最优决策点肯定比右边的最优决策点小(或大),这里直接分治即可,大概的代码如下:

void solve(int l, int r, int L, int R) {
    if (l > r)
        return;
    int mid = l + r >> 1, pos = L;
    for (int i = L; i <= min(R, mid - 1); i++) {
        if (calc(mid, pos) < calc(mid, i))
            pos = i;
    }
    if (pos < mid)
        f[mid] = max(f[mid], calc(mid, pos));
    solve(l, mid - 1, L, pos);
    solve(mid + 1, r, pos, R);
}

对于决策点和被决策点相互不独立时,需要用二分栈/队列的方法。

这需要一个引理:对于 \(w\) 满足四边形不等式的 DP 中 \(\forall i<j\) ,如果在 \(i\) 处决策点 \(n\)\(m\) 优秀(\(m<n\)),那么在 \(j\)\(n\) 同样比 \(m\) 优秀。证明如下:

\[对于 m<n<i<j ,我们有四边形不等式 w(m,j)+w(n,i)\ge w(m,i)+w(n,j).\\ 因为 n 点在 i 比 m 点更优秀,所以f_m + w(m,i)\le f_n+w(n,i)\\ 两式相加即可得到:f_m+w(m,j)\le f_n+w(m,j)\\ \]

那这样我们每增加一个点,去覆盖原来的一些点,因为引理,这些决策点有一个类似单调性的性质,所以可以用单调队列/单调栈来维护。这里大概讲一下原理:

加入原来对于每一个点的最佳决策点是 00112222234444666。

而加入了一个 \(7\) 我们可以二分出它的最左边的以它为最佳决策点的点。

那么由于引理,可以知道它右边所有点都是 \(7\) ,大概长这样:00112222234477777。

这里用一个单调队列,原先队列里的元素是 <0,,1,2,3,4,6> 更改之后的队列内元素就是<0,1,2,3,4,7>这里需要更新一下 \(4\) 的最佳右端点。

当然如果队头元素的最佳右端点小于现在所要决策的点,去掉即可,跟一般的单调队列优化 \(dp\) 有一点类似,就是多了一个二分最佳左端点的过程。

以上的方法都是 \(O(n\log n)\) 的。如果不想写二分单调队列的写法的话,可以用 \(O(n\log^2n)\) 的 cdq 写法。

void solve(int l, int r, int L, int R) {
    if (l > r)
        return;
    int mid = l + r >> 1, pos = L;
    for (int i = L; i <= min(R, mid - 1); i++) {
        if (calc(mid, pos) < calc(mid, i))
            pos = i;
    }
    if (pos < mid)
        f[mid] = max(f[mid], calc(mid, pos));
    solve(l, mid - 1, L, pos);
    solve(mid + 1, r, pos, R);
}
void cdq(int l, int r) {
    if (l < r) {
        int mid = l + r >> 1;
        cdq(l, mid);
        solve(mid + 1, r, l, mid);
        cdq(mid + 1, r);
    }
}

对于 2D/1D dp (石子合并类题目)就比较简单了。在枚举转移的一维中去用上一维来限制即可。

for(int len=2;len<=n;len++){
		for(int l=1;l+len-1<=n;l++){
			int r=l+len-1;
			for(int k=g[l][r-1];k<=g[l+1][r];k++){
				if(f[l][r]>f[l][k]+f[k+1][r]+pre[r]-pre[l-1]){
					f[l][r]=f[l][k]+f[k+1][r]+pre[r]-pre[l-1];
					g[l][r]=k;
				}
			}
			//cout<<f[l][r]<<endl;
		}
	}
posted @ 2025-07-13 14:39  hnczy  阅读(60)  评论(0)    收藏  举报