DP 学习笔记(三):与斜率有关的 DP 优化
几何无王者之道,惟有点与线之舞蹈。————笛卡尔
几何不光只有中考高考考的那三瓜两枣,在 DP 中也可以有非常重要的作用。
基础知识
斜率
函数的凹凸性
凸壳
斜率优化
这种优化算是后面要讲的决策单调性的特殊情况,不过没有决策单调性的性质也可以做。
洛谷 P2900 [USACO08MAR] Land Acquisition G
四边形不等式优化
应用场合
对于区间 DP,我们一般需要枚举一段区间的分割点,把当前区间拆分成两个小区间,把两个小区间的答案加起来再加上这两个区间合在一起的代价,也就是 \(dp_{i, j} = \displaystyle\min_{k = i}^{j - 1} dp_{i, k} + dp_{k + 1, j} + w_{i, j}\)(\(\max\) 也是一样的),朴素做法的时间复杂度是 \(O(n^3)\),但是如果 \(w_{i, j}\) 满足一些性质,那么我们可以将其优化到 \(O(n^2)\)(对于 DP 来说已经是一个非常好的复杂度了)。
四边形不等式的定义
我们先给出四边形不等式的定义:设 \(w\) 是定义在整数集合上的二元函数,对于任意整数 \(i \leq i' \leq j \leq j'\),如果有 \(w(i, j) + w(i', j') \leq w(i, j') + w(i', j)\)(也就是交叉的两个区间大于包含的两个区间),就称 \(w\) 满足四边形不等式。
为什么这个叫做四边形不等式,我们考虑将这四个区间画在数轴上:

我们把 \(i'\) 和 \(j'\) 提到一个更高的水平线上,再把每条线拉直,此时我们就能得到一个梯形:

此时我们要证明 \(AC + BD \geq AD + BC\),以下是它的证明:
在 \(\triangle AOD\) 中,运用三角形两边之和大于第三边,可以得到 \(AO + OD \geq AD\),同理,\(BO + OC \geq BC\),将等式左边相加,右边也相加,就可以得到 \(AO + BO + OC + OD \geq AD + BC\),即 \(AC + BD \geq AD + BC\)。你会发现这个式子和 \(w\) 满足的不等式正好相反,因此信息中的四边形不等式可以理解成几何中的反四边形不等式。而当你把这个四边形两个点缩成一个时,\(w\) 又满足反三角不等式,即两边之和小于第三边。
下面再给出二元函数单调性的定义:如果对于任意整数 \(i \leq i' \leq j \leq j'\),有 \(w(i, j') \geq w(i', j)\),则称 \(w\) 具有单调性,也就是大区间的权值大于小区间的权值。
四边形不等式定理
我们先来一些定义,我们设二元函数 \(c\) 满足:
这个 \(c\) 和之前的 DP 函数很像,就是把 \(w(i, j)\) 提到了外面去,其实本质是一样的。
这里的二元函数 \(w\) 也是有限制的,需要满足四边形不等式和单调性,即:
下面,我们先来证明两个引理。
引理 2.3.1
如果 \(w\) 满足四边形不等式和单调性,那么 \(c\) 也满足四边形不等式,即 \(c(i, j) + c(i', j') \leq c(i', j) + c(i, j'), i \leq i' \leq j \leq j'\)。
证明:
分以下 \(3\) 种情况讨论:
-
\(i = i'\) 或 \(j = j'\),此时左右的式子一模一样,显然成立。
-
\(i < i' = j < j'\),我们将这个式子带入引理 2.3.1,变成 \(c(i, j) + c(j, j') \leq c(i, j')\),现在我们要证明的,也就变成了这个式子。
我们设 \(c(i, j')\) 在 \(k = z\) 处有最小值,记为 \(c_z(i, j) = c(i, j) = w(i, j) + c(i, z) + c(z + 1, j)\)。
现在有两个对称的情况,\(z \leq j\) 和 \(z \geq j\),我们只证明 \(z \leq j\) 的情况,\(z \geq j\) 同理可得。
考虑数学归纳法,我们先证明 \(z \leq j \leq j'\) 时引理成立,此时我们设三角形三个顶点分别是 \(i, j, j'\),它们之间的边长分别是 \(c(i, j), c(j, j'), c(i, j')\),那么 \(z\) 就是 \(ij'\) 这条边上的一个最优分割点,于是我们可以画出下图:

在 \(\triangle zjj'\) 中,\(c(z, j) + c(j, j') \leq c(z + 1, j')\),这是反三角不等式此时满足引理 2.3.1。
我们考虑,\(z\) 为 \(i\) 到 \(j'\) 的区间最优分割点,但有可能是 \(i\) 到 \(j'\) 的最优分割点,因此 \(c(i, j) \leq c_z(i, j) = w(i, j) + c(i, z) + c(z + 1, j)\)。
我们把两边同时加上 \(c(j, j')\),可以得到 \(c(i, j) + c(j, j') \leq w(i, j) + c(i, z) + c(z + 1, j) + c(j, j')\)。由于单调性 \(w(i, j) \leq w(i, j')\),而根据之前求出的不等式 \(c(z, j) + c(j, j') \leq c(z, j')\),可以得出 \(c(i, j) + c(j, j') \leq w(i, j') + c(i, z) + c(z + 1, j') = c(i, j')\)。
-
\(i < i' < j < j'\),此时是最普通的情况。假设小区间 \(c(i', j)\) 和大区间 \(c(i, j')\) 分别在 \(k = y\) 和 \(k = z\) 处取得最小值,记为 \(c(i', j) = c_y(i', j) = w(i', j) + c(i', y) + c(y + 1, j)\),\(c(i, j') = c_z(i, j') = w(i, j') + c(i, z) + c(z + 1, j')\)。
现在又有两个对称的情况,\(z \leq y\) 和 \(z \geq y\),我们只证明 \(z \leq y\) 的情况,\(z \geq y\) 同理可得。
我们依然考虑数学归纳法,先证明边界情况,即 \(z \leq y < j < j'\) 时引理成立。我们设四边形的四个顶点是 \(i, i', j, j'\),它们之间的边长分别是 \(c(i, j'), c(j, j'), c(i', j), c(i, i')\),那么 \(y\) 就是 \(i'j\) 这条边上的最优分割点,而 \(z\) 就是 \(ij'\) 这条边上的最优分割点,于是我们可以画出下图:

根据四边形不等式,\(c(z, j) + c(y, j) \leq c(y + 1, j) + c(z + 1, j')\),此时满足引理 2.3.1。
依然考虑 \(y\) 是 \(i'\) 到 \(j\) 的最优分割点,可能是 \(i'\) 到 \(j'\) 的最优分割点,因此 \(c(i', j') \leq c_z(i', j')\),同理可得 \(c(i, j) \leq c_z(i, j)\),于是将等式左边加起来,等式右边加起来,可以得到 \(c(i, j) + c(i', j') \leq c_z(i, j) + c_y(i', j') = w(i, j) + w(i', j') + c(i, z) + c(z + 1, j) + c(i', y) + c(y + 1, j')\)。
根据 \(w\) 的单调性以及推出的 \(c(z, j) + c(y, j) \leq c(y + 1, j) + c(z + 1, j')\),可以得到 \(c(i, j) + c(i', j') \leq w(i', j) + w(i, j') + c(i, z) + c(i', y) + c(y + 1, j) + c(z + 1, j') = c(i', j) + c(i, j')\)。
现在就证明了引理 2.3.1。
引理 2.3.2
用 \(K_c(i, j)\) 表示使 \(c(i, j)\) 得到最小值的那些 \(k\) 中,最大的那个,那么引理 2.3.2 可以表示成 \(K_c(i, j) \leq K_c(i, j + 1) \leq K_c(i + 1, j + 1)\)。
证明:
当 \(i = j\) 时显然成立,考虑 \(i < j\) 的这种情况。
我们先证明 \(K_c(i, j) \leq K_c(i, j + 1)\)。这就相当于证明对于 \(i < k \leq k' \leq j\),若 \(c_{k'}(i, j) \leq c_k(i, j)\),则 \(c_{k'}(i, j + 1) \leq c_k(i, j + 1)\),也就是说,如果在 \([i, j]\) 这个区间内,存在比 \(k\) 更大的最优分割点 \(k'\),那么当拓展到区间 \([i, j + 1]\) 时,依然有 \(c_{k'}(i, j + 1) \geq c_k(i, j + 1)\),那么 \(k\) 就一定不是区间 \([i, j + 1]\) 的最大的最优分割点,\(k'\) 有可能是区间 \([i, j + 1]\) 的最大的最优分割点,但由于 \(k'\) 已经大于 \(k\),如果有更优的分割点,那它一定比 \(k\) 大了,此时就等价于 \(K_c(i, j) \leq K_c(i, j + 1)\)。
根据引理 2.3.1 的结论,若 \(k \leq k' \leq j < j + 1\),则有 \(c(k, j) + c(k', j + 1) \leq c(k', j) + c(k, j + 1)\)。
我们把不等式两边同时加上 \(w(i, j) + w(i, j + 1) + c(i, k) + c(i, k')\),根据 \(c\) 的定义式,有 \(c_k(i, j) + c_{k'}(i, j + 1) \leq c_{k'}(i, j) + c_k(i, j + 1)\),于是 \(c_{k'}(i, j + 1) \leq c_{k'}(i, j) + c_k(i, j + 1) - c_k(i, j)\)。
由于我们有 \(c_{k'}(i, j) \leq c_k(i, j)\),我们将不等式两边同时加上 \(c_k(i, j + 1)\),再移项,可以得到 \(c_{k'}(i, j) + c_k(i, j + 1) - c_k(i, j) \leq c_k(i, j + 1)\),对比上一个等式,就可以得到 \(c_{k'}(i + 1, j) \leq c_k(i, j + 1)\),那么此时我们就证明了引理 2.3.2。
定理 2.3.1(四边形不等式定理)
现在开始证明最主要的这个定理了。
四边形不等式定理的内容是,用 DP 计算所有 \(c(i, j)\) 的时间复杂度为 \(O(n^2)\)。
证明:
我们计算区间 DP 时,一般都是枚举区间长度,再枚举左端点,也就是按照 \(len = 1, 2, \dots n\) 的顺序进行枚举。当我们把 DP 从 \(c(i, j)\) 转移到 \(c(i, j + 1)\) 时,由于引理 2.3.2 的限制,只需要 \(K_c(i + 1, j + 1) - K_c(i, j)\) 次,就可以完成所有转移,那我们把 \(i\) 取所有值的情况都求出来:
将等式左边加在一起,右边加在一起,那么总枚举次数就是 \(K_c(n - len + 1, n + 1) - K_c(1, len + 1) < n = O(n)\),一共有 \(O(n)\) 种不同的 \(len\),因此总时间复杂度就是 \(O(n^2)\),完成了证明。
当求的东西从 \(\min\) 变成了 \(max\),只用将 \(w\) 满足的两个不等式的 \(\leq\) 改成 \(\geq\) 就可以了。
应用
其实在真正的比赛中,一般都是打表发现单调性与四边形不等式就直接用了,但在此处还是证明一下吧。
这道题目在区间 DP 时已经讲过了,但是可以用四边形不等式进一步优化成 \(O(n^2)\)。
我们已知它的 DP 转移方程是 \(dp_{i, j} = \displaystyle\min_{k = i + 1}^{j - 1} dp_{i, k - 1} + dp_{k + 1, j} + sum_{i, j}\)(\(sum\) 为 \(i\) 到 \(j\) 的石子数之和),很显然,因为每个石子堆石子数都是非负的,那么 \(sum_{i, j}\) 一定满足单调性,而在这个题中 \(sum_{i, j'} + sum_{i', j} = sum_{i, j} + sum_{i', j'}\),也满足四边形不等式,因此可以用四边形不等式定理优化成 \(O(n^2)\)。
但是我们要求最大值时,发现 \(sum_{i, j}\) 不满足大区间的权值小于小区间的权值,因此无法用四边形不等式优化,但是最大值有一个性质,那就是只会从转移区间的两个端点处转移(不想证明了),那么最大值只有考虑两个端点的 DP 值哪个更大就可以了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e2 + 9;
int n, a[N], dp[N][N], dp2[N][N], s[N][N], num[N], ans1 = 100000000, ans2 = 0;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
num[i] = num[i - 1] + a[i];
}
for(int i = 1; i <= n - 1; i++)
num[i + n] = num[i + n - 1] + a[i];
n = n * 2 - 1;
for(int i = 1; i <= n; i++)
s[i][i] = i;
for(int len = 2; len <= n; len++){
for(int i = 1; i <= n - len + 1; i++){
int j = i + len - 1;
dp[i][j] = 100000000;
for(int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
if(dp[i][k] + dp[k + 1][j] + num[j] - num[i - 1] < dp[i][j]){
dp[i][j] = dp[i][k] + dp[k + 1][j] + num[j] - num[i - 1];
s[i][j] = k;
}
dp2[i][j] = max(dp2[i][j - 1], dp2[i + 1][j]) + num[j] - num[i - 1];
}
}
n = (n + 1) / 2;
for(int i = 1; i <= n; i++){
ans1 = min(ans1, dp[i][n + i - 1]);
ans2 = max(ans2, dp2[i][n + i - 1]);
}
printf("%d\n%d", ans1, ans2);
return 0;
}
UVA10304 Optimal Binary Search Tree
这就是四边形不等式优化的来源。
其实这道题目和石子合并特别像,如果我们把合并过程看成一棵树,那么每堆石子的贡献就是石子的个数乘以这堆石子的深度,因此我们有类似的 DP 优化方法。
我们设 \(dp_{i, j}\) 为把 \(i\) 到 \(j\) 的数字建成二叉搜索树的权值最小值。首先根据二叉搜索树的性质,每个节点左儿子的权值总是小于右儿子的,而且这道题目还把权值排好了序,那么显然,我们要在 \([i, j]\) 找到一个 \(k\) 作为根,那么此时左右子树所有节点的深度全部加上了 \(1\),因此总权值就增加了 \(sum_{i, j} - e_k\),因此转移方程就是 \(dp_{i, j} = \displaystyle\min_{k = i}^{j - 1} dp_{i, k} + dp_{k + 1, j} + sum_{i, j} - e_k\),它的单调性和四边形不等关系和上一题证法类似,可以优化成 \(O(n^2)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 259;
int dp[N][N], s[N][N], sum[N], a[N], n;
signed main(){
while(~scanf("%lld", &n) && n){
memset(s, 0, sizeof(s));
memset(dp, 0x3f, sizeof(dp));
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
sum[i] = sum[i - 1] + a[i];
s[i][i] = i;
dp[i][i] = dp[i + 1][i] = dp[i][i - 1] = 0;
}
for(int len = 2; len <= n; len++){
for(int i = 1; i + len - 1 <= n; i++){
int j = i + len - 1;
for(int k = s[i][j - 1]; k <= s[i + 1][j]; k++){
if(dp[i][j] > dp[i][k - 1] + dp[k + 1][j] + sum[j] - sum[i - 1] - a[k]){
dp[i][j] = dp[i][k - 1] + dp[k + 1][j] + sum[j] - sum[i - 1] - a[k];
s[i][j] = k;
}
}
}
}
printf("%lld\n", dp[1][n]);
}
return 0;
}
为什么是先出加强版再出普通版????
我们设 \(dp_{i, j}\) 表示前 \(i\) 个村庄一共有 \(j\) 个邮局的最小距离和,和区间 DP 一样,我们考虑将其划分成两个小区间 \([1, k]\) 和 \([k + j, j]\)(至于为什么不能区间 DP,可以发现复杂度是 \(O(n^3)\),无法通过),在第 \(1\) 个区间内放 \(j - 1\) 个邮局,在第 \(2\) 个区间内放剩下的 \(1\) 个邮局,转移方程是 \(dp_{i, j} = \displaystyle\min_{k = j - 1}^n dp_{k, j - 1} + w_{k + 1, i}\)(其中 \(w_{i, j}\) 为 \([i, j]\) 的村庄内放 \(1\) 个邮局,\([i, j]\) 内所有村庄到邮局距离和的最小值),根据初中知识,我们知道如果村庄个数为奇数,那么就把邮局安排在最中间的村庄,否则就安排在中间两个村庄中的任意一个,可以很好预处理出。
现在我们来分析 \(w_{i, j}\) 这个函数。我们此时要证明 \(w_{i, j'} + w_{i', j} \leq w_{i', j'} + w_{i, j}\) 。其实四边形不等式定理还有一个等价命题,那就是若对于 \(i < i + 1 \leq j < j + 1\),有 \(w_{i, j} + w_{i + 1, j + 1} \leq w_{i, j + 1} + w_{i + 1, j}\),那么称 \(w\) 满足四边形不等式。因此我们考虑 \(w_{i, j}\) 与 \(w_{i, j - 1}\) 的关系,由于邮局总是处于中间位置,那么有 \(w_{i, j} = w_{i, j - 1} + a_j - a_{\lfloor\frac{i + j}{2}\rfloor}\),那么 \(w_{i, j + 1} - w_{i, j} = a_{j + 1} - a_{\lfloor\frac{i + j + 1}{2}\rfloor}\),而 \(w_{i + 1, j + 1} - w_{i + 1, j} = a_{j + 1} - a_{\lfloor\frac{i + j + 2}{2}\rfloor}\),两式相减可以得到 \(w_{i, j + 1} + w_{i + 1, j} - w_{i, j} - w_{i + 1, j + 1} = a_{\lfloor\frac{i + j + 2}{2}\rfloor} - a_{\lfloor\frac{i + j + 1}{2}\rfloor}\),由于所给的 \(a\) 是单增的,因此这个值一定大于 \(0\),因此 \(w_{i, j} + w_{i + 1, j + 1} \geq w_{i, j + 1} + w_{i + 1, j}\),满足四边形不等式。而根据 \(w_{i, j}\) 的递推式,一定是大区间的权值大于小区间的权值,因此可以通过四边形不等式优化。
由于本题不是一般的区间 DP,我们求 \(dp_{i, j}\) 要用到 \(dp_{i + 1, j}\) 的最优分割点,因此 \(i\) 这一维需要倒序枚举。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e2 + 9, M = 3e3 + 9;
int a[M], dis[M][M], f[M][N], s[M][N], n, m;
signed main(){
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++){
dis[i][i] = 0;
for(int j = i + 1; j <= n; j++)
dis[i][j] = dis[i][j - 1] + a[j] - a[(i + j) >> 1];
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for(int j = 1; j <= m; j++){
s[n + 1][j] = n;
for(int i = n; i >= 1; i--)
for(int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
if(f[i][j] > f[k][j - 1] + dis[k + 1][i]){
f[i][j] = f[k][j - 1] + dis[k + 1][i];
s[i][j] = k;
}
}
printf("%lld", f[n][m]);
return 0;
}
这道题目和上一道,都是将 \(n\) 个数划分成 \(m\) 个区间的问题。
我们设第 \(i\) 天走的路程为 \(v_i\),考虑方差的计算公式 \(s^2 = \displaystyle\frac{\sum\limits_{i = 1}^m (\overline v - v_i)^2}{m} = \frac{\sum\limits_{i = 1}^m \left(\frac{\sum\limits_{i = 1}^m v_i}{m} - v_i \right)}{m}^2\)。
将平方展开,可以得到原式
乘以 \(m^2\) 后,可以得到 \(\left(\displaystyle\sum_{i = 1}^m v_i \right)^2 - 2\left(\displaystyle\sum_{i = 1}^m v_i \right)^2 + m\displaystyle\sum_{i = 1}^m v_i^2 = m\sum_{i = 1}^m v_i^2 - \left(\sum_{i = 1}^m v_i \right)^2\)。此时我们会发现后一项为定值,于是我们要让前一项尽可能小。
此时我们就可以开始 DP 了,设 \(dp_{i, j}\) 表示前 \(i\) 段路用 \(j\) 天走,每天走的路程的平方和,那我们考虑第 \(i\) 天走了多少距离,那么 \(dp_{i, j} = \displaystyle\min_{k = 1}^i dp_{k, j - 1} + (sum_i - sum_{k - 1})^2\)。
现在的价值函数变成了 \((sum_i - sum_j)^2(j < i)\),于是我们现在要证明大区间的权值大于小区间的权值,且交叉优于包含。第一个单调性显然成立,第二个相当于证明 \((sum_{i + 1} - sum_j)^2 + (sum_i - sum_{j + 1})^2 \leq (sum_{i + 1} - sum_{j + 1})^2 + (sum_i - sum_j)^2\)。
将两边的式子拆开,可以得到 \(sum_{i + 1}sum_j + sum_isum_{j + 1} \leq sum_{i + 1}sum_{j + 1} + sum_isum_j \iff 2sum_isum_j + a_{i + 1} sum_j + a_{j + 1} sum_i \leq 2sum_isum_j + a_{i + 1} sum_j + a_{j + 1} sum_i + a_{i + 1} a_{j + 1}\),那么可以得到 \(a_{i + 1}a_{j + 1} \geq 0\),这是显然成立的,那么四边形不等式证明成立。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e3 + 2;
int a[N], sum[N], f[N][N], s[N][N], n, m;
signed main(){
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
sum[i] = sum[i - 1] + a[i];
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for(int j = 1; j <= m; j++){
s[n + 1][j] = n;
for(int i = n; i >= 1; i--)
for(int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
if(f[i][j] > f[k][j - 1] + (sum[i] - sum[k]) * (sum[i] - sum[k])){
f[i][j] = f[k][j - 1] + (sum[i] - sum[k]) * (sum[i] - sum[k]);
s[i][j] = k;
}
}
printf("%lld", f[n][m] * m - sum[n] * sum[n]);
return 0;
}
拓展:\(\text{Garsia-Wachs}\) 算法
决策单调性优化
DP 也是一种数据结构,它维护了数组。———— hfu
这在决策单调性中很有体现。
应用场合
其实,我们之前做洛谷 P4767 [IOI 2000] 邮局 加强版时已经发现它不再是标准的区间 DP 了,如果去掉邮局的个数限制,那么这道题就变成了一维的 DP。当 \(n\) 比较大时,已经存不了区间决策点了,也只能用一维 DP。其实一维的 DP 也可以用四边形不等式优化,不过此时它就叫做决策单调性优化。
对于形如 \(dp_i = \displaystyle\min_{j = 0}^{i - 1} dp_j + w_{j, i}\) 的 DP 转移方程,我们设 \(p_i\) 是使 \(dp_j + w_{j, i}\) 取得最小值的 \(j\),即 \(j\) 是 \(i\) 的最优决策点,那么如果 \(w_{i, j}\) 满足四边形不等式,即 \(w_{i, j'} + w_{i', j} \geq w_{i, j} + w_{i', j'}\),那么 \(p_i\) 是单调不降的。
证明:
对于 \(i \in [i, n], j \in [0, p_i - 1]\),根据 \(p_i\) 的定义,有 \(dp_{p_i} + w_{p_i, i} \leq dp_j + w_{j, i}\)。而对于 \(i' \in [i + 1, n]\),由于 \(w\) 满足四边形不等式,那么 \(w_{j, i'} + w_{p_i, i} \geq w_{j, i} + w_{p_i, i'}\),移项可以得到 \(w_{p_i, i'} - w_{p_i, i} \leq w_{j, i'} - w_{j, i}\),将这个不等式加上 \(dp_{p_i} + w_{p_i, i} \leq dp_j + w_{j, i}\),最终可以得到 \(dp_{p_i} + w_{p_i, i'} \leq dp_j + w_{j, i'}\),这也就是说,小于 \(p_i\) 的决策点,肯定不会成为 \(i'\) 的决策点,那么 \(p_{i'}\) 一定 \(\geq p_i\),那么决策点就是单调不降的了。
另一种决策单调性:对于形如 \(dp_i = \displaystyle\max_{j = 0}^{i - 1} dp_j + w_{j, i}\) 的 DP 转移方程,如果 \(w_{i, j}\) 满足四边形不等式,即 \(w_{i, j'} + w_{i', j} \geq w_{i, j} + w_{i', j'}\),那么 \(p_i\) 是单调不升的。
证明:
对于 \(i \in [i, n], j \in [p_i + 1, i - 1]\),根据 \(p_i\) 的定义,有 \(dp_{p_i} + w_{p_i, i} \geq dp_j + w_{j, i}\)。而对于 \(i' \in [i + 1, n]\),由于 \(w\) 满足四边形不等式,那么 \(w_{j, i} + w_{p_i, i'} \geq w_{j, i'} + w_{p_i, i}\),移项可以得到 \(w_{p_i, i'} - w_{p_i, i} \geq w_{j, i'} - w_{j, i}\),将这个不等式加上 \(dp_{p_i} + w_{p_i, i} \geq dp_j + w_{j, i}\),最终可以得到 \(dp_{p_i} + w_{p_i, i'} \geq dp_j + w_{j, i'}\),这也就是说,大于 \(p_i\) 的决策点,肯定不会成为 \(i'\) 的决策点,那么 \(p_{i'}\) 一定 \(\leq p_i\),那么决策点就是单调不升的了。
应用
运用决策单调性可以把形如 \(dp_i = \displaystyle\min_{j = 0}^{i - 1} dp_j + w_{j, i}\) 或 \(dp_i = \displaystyle\min_{j = 0}^{i - 1} f_j + w_{j, i}\) 的式子从 \(O(n^2)\) 优化到 \(O(n \log n)\)。
二分队列
此做法适用于价值函数满足交叉优于包含,且求 \(\min\) 的 DP,或是价值函数满足包含优于交叉,且求 \(\max\) 的 DP。
我们维护一个队列,其中每个元素都是三元组 \((l, r, p)\) 的形式,它表示 \(dp_l\) 到 \(dp_r\) 的决策点是 \(p\)。
我们枚举每个 \(i\)。首先我们先计算出这个 \(i\) 的 DP 值(如果 \(i\) 的决策点可以是 \(i\),那么就先进行后续操作,再计算 \(dp_i\)), 然后我们考虑 \(i\) 会成为哪些 \(i' > i\) 的决策点,很显然,我们一定会找到一个分界线,在分界线之前 \(p\) 数组存储的决策点都比 \(i\) 好,之后的都比 \(i\) 差。我们只需要比较一下在某一段 \((l, r, p)\),在 \(l\) 处是 \(p\) 更优还是 \(i\) 更优,如果在 \(l\) 处 \(i\) 都比 \(p\) 优秀,那么直接将这一段丢掉即可。同样,如果在某一段 \((l, r, p)\),在 \(r\) 处 \(p\) 都比 \(i\) 优秀,那么 \(i\) 就无法继续更新了。
不过还有一点,就是分割点可以在某段 \([l, r]\) 中间,此时我们需要二分查找出该点。
此外,如果一个决策点 \(j\) 决策的区间 \([l, r]\) 中 \(r\) 已经小于 \(i\) 了,那么此时 \(j\) 已经无法决策 \(i\) 和 \(i\) 以后的 DP 值了,直接将其从队头弹出,那么剩下的队头这个决策点的决策区间一定包含 \(i\) 了,直接转移即可。
白日依山尽,黄河入海流。
欲穷千里目,更上一层楼。————题目样例
我们设 \(dp_i\) 表示前 \(i\) 句诗排版后的最小不协调值,那我们考虑第 \(i\) 句诗这一行有几句诗,也就是考虑上一个换行的地方在哪里。于是我们记 \(sum_i\) 表示前 \(i\) 句诗的长度和,于是 \(dp_i = \displaystyle\min_{0 \leq j < i} dp_j + |sum_i - sum_j + i - j - 1 - L|^P\)(加上 \(i - j - 1\) 是因为每句诗之间有空格)。
这又是这种区间划分的形式,由于有 \(P\) 次方,不方便用单调队列优化,于是我们考虑决策单调性。
那么现在,我们需要证明 \(w_{i, j} = |sum_i - sum_j + i - j - 1 - L|^P\) 满足四边形不等式,也就是 \(w_{j, i} + w_{j + 1, i + 1} \leq w_{j, i + 1} + w_{j + 1, i}\)。我们设 \(u = (sum_i + i) - (sum_j + j) - (L + 1), v = (sum_i + 1) - (sum_{j + 1} + j + 1) - (L + 1)\),那么 \(w_{j, i + 1} = |sum_i - sum_{j + 1} + (i - j - 2) - L|^P = |v|^P\),同理可得 \(w_{j + 1, i + 1} = |v + (a_{i + 1} + 1)|^P, w_{j, i} = |u|^2, w_{j, i + 1} = |u + (a_{i + 1} + 1)|^P\),那么将原式移项后,最终要证明 \(|v|^P - |v + a_{i + 1} + 1|^P \geq |u|^P - |u + a_{i + 1} + 1|^P\)。
观察两边式子的形式,可以用 \(y = |x|^P - |x + C|^P\) 来刻画这个不等式,由于 \(sum_{j + 1} \geq sum_j\),\(v\) 比 \(u\) 还多减了一个 \(1\),那么 \(u\) 一定大于 \(v\),因此我们需要证明这个函数在 \(C > 0\) 时单调不升。
由于含有绝对值,因此我们用零点分段法来证明:
-
\(0 \leq x\),此时原函数就变成了 \(x^P - (x + C)^P\),运用求导的性质,该函数的导数是 \(Px^{P - 1} - P(x + C)^{P - 1}\)。\(P = 1\) 时该导数为 \(0\),满足条件,而 \(P > 1\) 时 \(x^{P - 1} < (x + C)^{P - 1}\),此时导函数一直小于 \(0\),原函数也是单减的;
-
\(-C \leq x < 0\),此时原函数的导函数是 \(-Px^{P - 1} - P(x + C)^{P - 1}\)。若 \(P\) 是奇数,那么就是一个负数减一个正数,结果一定为负,若 \(P\) 是偶数,由于 \(x < 0\),因此还是一个负数减一个正数的形式,结果还是负的;
-
\(x \leq -C\),此时原函数的导函数就是 \(-Px^{P - 1} + P(x + C)^{P - 1}\)。若 \(P\) 是偶数,由于 \(x < 0\),因此是一个负数减一个正数的形式,结果为负。若 \(P\) 是奇数,由于此时 \(|x| > |x + C|\),因此减数要比被减数大,结果依然为负。
此时我们证明了该函数单调递减,从而证明了四边形不等式。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e6 + 5, INF = 1e18;
int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')
f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
struct Node{
int l, r, pos;
} q[N];
int s[N], pre[N], n, L, k, T, head, tail;
char poem[N][31];
bool flag[N];
long double dp[N];
long double qpow(long double a, int b){
long double res = 1;
while(b > 0){
if(b & 1)
res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
long double w(int j, int i){
return dp[j] + qpow(abs(s[i] - s[j] + i - j - 1 - L), k);
}
int bs(int id, int x){
int res = q[id].r, l = q[id].l, r = q[id].r;
while(l <= r){
int mid = (l + r) >> 1;
if(w(q[id].pos, mid) >= w(x, mid)){
res = mid;
r = mid - 1;
} else
l = mid + 1;
}
return res;
}
void insert(int i){
if(head <= tail && q[head].r < i)
head++;
else
q[head].l = i;
if(dp[i] > w(q[head].pos, i)){
dp[i] = w(q[head].pos, i);
pre[i] = q[head].pos;
}
while(head <= tail && w(i, q[tail].l) <= w(q[tail].pos, q[tail].l))
tail--;
if(head > tail)
q[++tail] = Node{i, n, i};
else if(w(i, q[tail].r) >= w(q[tail].pos, q[tail].r)){
if(q[tail].r == n)
return;
q[tail + 1] = Node{q[tail].r + 1, n, i};
tail++;
} else {
int p = bs(tail, i);
q[tail].r = p - 1;
q[++tail] = Node{p, n, i};
}
}
signed main(){
T = read();
while(T--){
for(int i = 1; i <= n; i++){
dp[i] = 0;
pre[i] = 0;
flag[i] = 0;
}
for(int i = 1; i <= tail; i++)
q[i] = Node{0, 0, 0};
n = read();
L = read();
k = read();
for(int i = 1; i <= n; i++){
scanf("%s", poem[i]);
s[i] = s[i - 1] + strlen(poem[i]);
dp[i] = w(0, i);
}
head = 1;
tail = 0;
for(int i = 1; i <= n; i++)
insert(i);
if(dp[n] > 1e18)
printf("Too hard to arrange\n");
else {
printf("%.0Lf\n", dp[n]);
for(int i = n; i; i = pre[i])
flag[i] = true;
for(int i = 1; i <= n; i++){
printf("%s", poem[i]);
if(flag[i])
printf("\n");
else
printf(" ");
}
}
printf("--------------------\n");
}
return 0;
}
洛谷 P3515 [POI 2011] Lightning Conductor
我们设 \(dp_i\) 表示在第 \(i\) 个建筑物上修建避雷针的最小高度,由于避雷针必须保护所有建筑物,那么答案就必须取保护每一个建筑物的避雷针最小高度的最大值,于是 \(dp_i = \displaystyle\max_{j = 1}^n h_j - h_i + \sqrt{|i - j|}\),其中绝对值可以通过正着扫一遍,倒着扫一遍去掉,于是 \(dp_i = \displaystyle\max_{j = 1}^i h_j - h_i + \sqrt{i - j} = \max_{j = 1}^i (h_j + \sqrt{i - j}) - h_i\)。我们此时可以认为 \(j\) 为 \(dp_i\) 的决策点,于是价值函数就变成了 \(\sqrt{i - j}\)。
我们现在要证明 \(\sqrt{i - j}\) 满足包含优于交叉,也就是 \(\sqrt{i - (j + 1)} + \sqrt{(i + 1) - j} \leq \sqrt{i - j} + \sqrt{(i + 1) - (j + 1)}\)。我们设 \(i - j = x\),于是原式就变成了 \(2 \sqrt x \geq \sqrt{x + 1} + \sqrt{x - 1}\),移项后变成 \(\sqrt{x + 1} - \sqrt x \leq \sqrt x - \sqrt{x - 1}\),现在我们要证明的就是这个式子。
我们构造函数 \(y = \sqrt{x + 1} - \sqrt x\),对它求导可以得到 \(y = \displaystyle\frac 12 (x + 1)^{-\frac 12} - \frac 12 x^{-\frac 12}\),由于 \(x + 1 > x\),那么 \((x + 1)^{-\frac 12} < x^{-\frac 12}\),于是导函数是个减函数,且斜率恒为负,因此原函数就是一个减函数,因此 \(\sqrt{x + 1} - \sqrt x \leq \sqrt x - \sqrt{x - 1}\),满足四边形不等式。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 9;
struct Node{
int l, r, pos;
} q[N];
int h[N], n, head, tail;
double dp[N];
double w(int j, int i){
return h[j] + sqrt((i - j) * 1.0);
}
int bs(int id, int x){
int res = q[id].r + 1, l = q[id].l, r = q[id].r;
while(l <= r){
int mid = (l + r) >> 1;
if(w(q[id].pos, mid) <= w(x, mid)){
res = mid;
r = mid - 1;
} else
l = mid + 1;
}
return res;
}
void insert(int i){
if(head <= tail && q[head].r < i)
head++;
else
q[head].l = i;
dp[i] = max(dp[i], w(q[head].pos, i));
while(head <= tail && w(i, q[tail].l) >= w(q[tail].pos, q[tail].l))
tail--;
if(head > tail)
q[++tail] = Node{i, n, i};
else if(w(i, q[tail].r) <= w(q[tail].pos, q[tail].r)){
if(q[tail].r == n)
return;
q[tail + 1] = Node{q[tail].r + 1, n, i};
tail++;
} else {
int p = bs(tail, i);
q[tail].r = p - 1;
q[++tail] = Node{p, n, i};
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &h[i]);
dp[i] = h[i];
}
head = 1;
tail = 0;
for(int i = 1; i <= n; i++)
insert(i);
for(int i = 1; i <= n / 2; i++){
swap(h[i], h[n - i + 1]);
swap(dp[i], dp[n - i + 1]);
}
head = 1;
tail = 0;
for(int i = 1; i <= n; i++)
insert(i);
for(int i = n; i >= 1; i--)
printf("%d\n", int(ceil(dp[i]) - h[i]));
return 0;
}
二分栈
此做法适用于价值函数满足交叉大于包含,且求 \(\max\) 的 DP,或是价值函数满足包含大于交叉,且求 \(\min\) 的 DP。
和二分队列很像,只是这次我们维护的是栈,因为栈满足先进后出,那么越先进入的点就越有可能成为越后进入的点的决策点。
我们依然枚举每个 \(i\),只是这次,如果要替换一整段 \((l, r, p)\),我们要比较在 \(r\) 处谁更优,如果一整段都无法被替换,我们要比较在 \(l\) 处谁更优。二分的符号也要反一下,其它就和二分队列一摸一样了。
我们设 \(dp_i\) 表示以 \(i\) 结尾,把 \([1, i]\) 所有贝壳都变成柠檬,柠檬个数的最大值。首先粗略估计一下 \(i\) 的决策点,发现它一定与 \(i\) 的大小相同,这是因为如果你多往左选一个,此时变出来的柠檬个数没有变化,还减少了之前一段决策的方案,不会增加柠檬数,于是从与 \(i\) 大小相同的地方转移一定不劣,于是我们可以写出以下转移方程:\(dp_i = \displaystyle\max_{s_j = s_i} dp_{j - 1} + s_i (rk_i - rk_j + 1)^2\)(\(rk_i\) 表示 \(i\) 在所有 \(s_j = s_i\) 中的排名)。
于是我们每种颜色分开考虑。此时的价值函数是 \(s_i (rk_i - rk_j + 1)^2\),这又是一个二次函数,而二次函数满足四边形不等式的证明,我们已经在洛谷 P4072 [SDOI2016] 征途给过了,此时我们就可以用二分栈优化了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 9;
struct Node{
int l, r, pos;
};
vector <int> mp[N];
vector <Node> st[N];
int s[N], dp[N], cnt[N], rk[N], n;
int w(int j, int i, int c){
return dp[mp[c][j] - 1] + (i - j + 1) * (i - j + 1) * c;
}
int bs(int x, int c){
int res = st[c].back().l, l = st[c].back().l, r = st[c].back().r;
while(l <= r){
int mid = (l + r) >> 1;
if(w(st[c].back().pos, mid, c) >= w(x, mid, c)){
res = mid;
r = mid - 1;
} else
l = mid + 1;
}
return res;
}
void insert(int i, int c){
while(!st[c].empty() && w(i, st[c].back().r, c) >= w(st[c].back().pos, st[c].back().r, c))
st[c].pop_back();
if(st[c].empty())
st[c].push_back(Node{i, cnt[c], i});
else if(w(i, st[c].back().l, c) <= w(st[c].back().pos, st[c].back().l, c)){
if(st[c].back().l != i)
st[c].push_back(Node{i, st[c].back().l - 1, i});
} else {
int p = bs(i, c);
st[c].back().l = p;
st[c].push_back(Node{i, p - 1, i});
}
if(!st[c].empty() && st[c].back().r < i)
st[c].pop_back();
else
st[c].back().l = i;
dp[mp[c][i]] = w(st[c].back().pos, i, c);
}
signed main(){
scanf("%lld", &n);
for(int i = 1; i <= n; i++){
scanf("%lld", &s[i]);
rk[i] = ++cnt[s[i]];
if(cnt[s[i]] == 1)
mp[s[i]].push_back(0);
mp[s[i]].push_back(i);
}
for(int i = 1; i <= n; i++)
insert(rk[i], s[i]);
printf("%lld", dp[n]);
return 0;
}
分治
这也是一种常见的决策单调性优化 DP 的写法,而且比单调队列还好理解,我们设 DP(int l, int r, int ql, int qr) 表示当前要计算 \([l, r]\) 区间内的 DP 值,且决策点在 \([pl, pr]\) 中。
每次我们暴力算出区间中点处的 DP 值,并记录它的决策点,那么左区间的决策点一定在当前决策点左边,右区间的决策点一定在当前决策点右边,此时就可以分治往下做了。
不过分治并不能解决一切决策单调性问题,它只能解决一层一层 DP 的问题,如果不是一层一层解决问题,很有可能当前分治中心的决策点的 DP 值还没有算出,导致程序出现问题。
这道题目就是典型的区间拆分问题,DP 方程就是经典的 \(dp_{i, j} = \displaystyle\min_{k = 1}^i dp_{k, j - 1} + w_{k + 1, i}\)(这里的 \(w_{i, j}\) 表示 \(i\) 到 \(j\) 的顺序对个数)。
我们现在要证明 \(w\) 满足交叉优于包含,也就是 \(w_{j, i} + w_{j + 1, i + 1} \leq w_{j, i + 1} + w_{j + 1, i}\)。右边相当于 \(w_{i, j}\) 加上 \(w_{i, j}\),再加上 \([j, i]\) 中 \(a\) 小于 \(a_{i + 1}\) 的数字个数,减去 \([j, i + 1]\) 中 \(a\) 大于 \(a_j\) 的数字个数;左边相当于 \(w_{i, j}\) 加上 \([j, i]\) 中 \(a\) 小于 \(a_{i + 1}\) 的数字个数,再减去 \([j, i]\) 中 \(a\) 大于 \(a_j\) 的数字个数。我们不知道 \(a_{i + 1}\) 与 \(a_j\) 那个大,如果 \(a_{i + 1} < a_j\),那么\([j, i + 1]\) 中 \(a\) 大于 \(a_j\) 的数字个数就等于 \([j, i]\) 中 \(a\) 大于 \(a_j\) 的数字个数;如果 \(a_{i + 1} > a_j\),那么\([j, i + 1]\) 中 \(a\) 大于 \(a_j\) 的数字个数就大于 \([j, i]\) 中 \(a\) 大于 \(a_j\) 的数字个数,因此原式左边一定小于右边,满足四边形不等式,可以优化。
还有一点,这道题区间的贡献不能 \(O(1)\) 算,但是我们可以像莫队一样暴力转移,复杂度只会多一个 \(\log\),这是树状数组的复杂度,而莫队不会增加复杂度。可以感性理解一下,看一下下面的代码,我们会发现,除了从上一层转移到下一层时 \(L, R\) 会有 \(O(n)\) 的变化,其他时候都是左端点往右移一位,这是 \(O(1)\) 的,那么每层都只会多出 \(O(n)\) 的时间复杂度,因此可以直接莫队。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2.5e4 + 9, K = 29;
int a[N], dp[N][K], n, m;
int t[N];
int lowbit(int x){
return x & -x;
}
void insert(int i, int x){
for(; i <= n; i += lowbit(i))
t[i] += x;
}
int ask(int i){
int res = 0;
for(; i > 0; i -= lowbit(i))
res += t[i];
return res;
}
int L = 1, R, ans;
int w(int l, int r){
while(l < L){
ans += R - L + 1;
ans -= ask(a[--L]);
insert(a[L], 1);
}
while(R < r){
ans += ask(a[++R]);
insert(a[R], 1);
}
while(L < l){
insert(a[L], -1);
ans += ask(a[L++]);
ans -= R - L + 1;
}
while(r < R){
insert(a[R], -1);
ans -= ask(a[R--]);
}
return ans;
}
void dac(int l, int r, int ql, int qr, int k){
if(l > r)
return;
int mid = (l + r) >> 1, pos = ql;
dp[mid][k] = 0x3f3f3f3f;
for(int i = ql; i <= min(qr, mid); i++)
if(dp[i - 1][k - 1] + w(i, mid) < dp[mid][k]){
dp[mid][k] = dp[i - 1][k - 1] + w(i, mid);
pos = i;
}
dac(l, mid - 1, ql, pos, k);
dac(mid + 1, r, pos, qr, k);
}
int res = 0x3f3f3f3f;
signed main(){
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++)
dp[i][0] = 0x3f3f3f3f;
for(int j = 1; j <= m; j++){
dac(1, n, 1, n, j);
res = min(res, dp[n][j]);
}
printf("%lld", res);
return 0;
}
CF868F Yet Another Minimization Problem
这道题目和上一道基本一样,转移方程还是 \(dp_{i, j} = \displaystyle\min_{k = 1}^i dp_{k, j - 1} + w_{k + 1, i}\),不过这里的 \(w_{i, j}\) 表示 \([i, j]\) 中相同颜色对数。
我们此处还是要证明 \(w_{j, i} + w_{j + 1, i + 1} \leq w_{j, i + 1} + w_{j + 1, i}\),证法和上一题一样,只是这次区间 \([i, j]\) 往左右移动时加上或减去的是区间中与端点数字相同的数字个数,然后就可以莫队了。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 9, K = 21, INF = 1e18;
int a[N], dp[N][K], n, m;
int cnt[N];
int L = 1, R, ans;
int w(int l, int r){
while(l < L){
ans += cnt[a[--L]];
cnt[a[L]]++;
}
while(R < r){
ans += cnt[a[++R]];
cnt[a[R]]++;
}
while(L < l){
cnt[a[L]]--;
ans -= cnt[a[L++]];
}
while(r < R){
cnt[a[R]]--;
ans -= cnt[a[R--]];
}
return ans;
}
void dac(int l, int r, int ql, int qr, int k){
if(l > r)
return;
int mid = (l + r) >> 1, pos = ql;
dp[mid][k] = INF;
for(int i = ql; i <= min(qr, mid); i++)
if(dp[i - 1][k - 1] + w(i, mid) < dp[mid][k]){
dp[mid][k] = dp[i - 1][k - 1] + w(i, mid);
pos = i;
}
dac(l, mid - 1, ql, pos, k);
dac(mid + 1, r, pos, qr, k);
}
int res = INF;
signed main(){
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++)
dp[i][0] = INF;
for(int j = 1; j <= m; j++){
dac(1, n, 1, n, j);
res = min(res, dp[n][j]);
}
printf("%lld", res);
return 0;
}
这道题基本上算是上一道题目的双倍经验了,证法不再赘述。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3.5e4 + 9, K = 59;
int a[N], dp[N][K], n, m;
int cnt[N];
int L = 1, R, ans;
int w(int l, int r){
while(l < L){
cnt[a[--L]]++;
if(cnt[a[L]] == 1)
ans++;
}
while(R < r){
cnt[a[++R]]++;
if(cnt[a[R]] == 1)
ans++;
}
while(L < l){
if(cnt[a[L]] == 1)
ans--;
cnt[a[L++]]--;
}
while(r < R){
if(cnt[a[R]] == 1)
ans--;
cnt[a[R--]]--;
}
return ans;
}
void dac(int l, int r, int ql, int qr, int k){
if(l > r)
return;
int mid = (l + r) >> 1, pos = ql;
for(int i = ql; i <= min(qr, mid); i++)
if(dp[i - 1][k - 1] + w(i, mid) > dp[mid][k]){
dp[mid][k] = dp[i - 1][k - 1] + w(i, mid);
pos = i;
}
dac(l, mid - 1, ql, pos, k);
dac(mid + 1, r, pos, qr, k);
}
int res;
signed main(){
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int j = 1; j <= m; j++){
dac(1, n, 1, n, j);
res = max(res, dp[n][j]);
}
printf("%lld", res);
return 0;
}
线段树分治 + 分治
洛谷 P5244 [USACO19FEB] Mowing Mischief P
总结
二维 DP 可以用四边形不等式优化的条件:
-
求 \(\min\),价值函数满足交叉优于包含,且大区间的权值大于小区间的权值;
-
求 \(\max\),价值函数满足包含优于交叉,且大区间的权值小于小区间的权值。
一维 DP 可以用二分队列优化:
-
求 \(\min\),价值函数满足交叉优于包含;
-
求 \(\max\),价值函数满足包含优于交叉。
一维 DP 可以用二分栈优化:
-
求 \(\min\),价值函数满足包含优于交叉;
-
求 \(\max\),价值函数满足交叉优于包含。
WQS 二分
当上述决策单调性题目的 \(n\) 和 \(m\) 都增大到 \(10^5\),似乎单调队列也不能用了,但是我们可以继续用 WQS 二分优化。
WQS 二分主要解决从 \(n\) 个物品中选 \(m\) 次物品,每个物品有权值,求能选出的的物品的最大 / 最小权值和。
这类问题的难点在于只能选 \(m\) 次物品,可能无法达到最优,这会让我们的问题变得复杂,我们考虑现将其简化成没有限制的情况,我们
洛谷 P6246 [IOI 2000] 邮局 加强版 加强版
AT_arc168_e Subsegments with Large Sums
Slope Trick
-
黄 kx、付 ym、吴 g 的课件
-
算法竞赛 罗勇军、郭卫斌
-
算法竞赛进阶指南 李煜东
-
决策单调性 JueFan
-
[POI2011]Lightening Conductor(决策单调性) ATS_nantf
-
DP的决策单调性优化总结 command_block
-
关于WQS二分算法以及其一个细节证明 Creeper_LKF
-
【学习笔记】WQS二分详解及常见理解误区解释 ikrvxt
-
wqs二分 花子の水晶植轮daisuki
本文来自博客园,作者:Orange_new,转载请注明原文链接:https://www.cnblogs.com/JPGOJCZX/p/18422833

浙公网安备 33010602011771号