DP 优化——决策单调性优化
决策单调性优化
决策单调性定义
对于 \(dp_i\),若 \(dp_i\) 由 \(dp_j\) 转移最优,则 \(j\) 即为 \(dp_i\) 的最优决策点。
在状态转移方程 \(dp_i=min\{dp_j+w(j,i)\}^*\),\(j\in[0,i)\) 中,设 \(p_i\) 为 \(dp_i\) 的最优决策点,若 \(p\) 在 \([1,n]\) 上单调不减,则称 \(dp\) 具有决策单调性
\(*\):\(w(a,b)\) 是定义在整数集合上的二元函数,下同。
四边形不等式
定义
若函数 \(w(x,y)\) 满足对于 \(\forall a,b,c,d\in\mathbb Z\),\(a\le b\le c\le d\),都有 \(w(a,d)+w(b,c)\ge w(a,c)+w(b,d)\),则称函数 \(w(x,y)\) 满足四边形不等式。
推论
若函数 \(w(x,y)\) 满足对于 \(\forall a,b\in\mathbb Z\),\(a\le b\),都有 \(w(a+1,b)+w(a,b+1)\ge w(a,b)+w(a+1,b+1)\),则称函数 \(w(x,y)\) 满足四边形不等式。
证明
对于 \(a<c\),有 \(w(a,c+1)+w(a+1,c)\ge w(a,c)+w(a+1,c+1)\);
对于 \(a+1<c\),有 \(w(a+1,c+1)+w(a+2,c)\ge w(a+1,c)+w(a+2,c+1)\);
两式相加,整理得 \(w(a,c+1)+w(a+2,c)\ge w(a,c)+w(a+2,c+1)\);
以此类推,对于任意的 \(a\le b\le c\),有 \(w(a,c+1)+w(b,c)\ge w(a,c)+w(b,c+1)\);
同理,对任意的\(a\le b\le c\le d\),有 \(w(a,d)+w(b,c)\ge w(a,c)+w(b,d)\)。
决策单调性与四边形不等式
定理
在状态转移方程 \(dp_i=min\{dp_j+w(j,i)\}\),\(j\in[0,i)\) 中,若函数 \(w\) 满足四边形不等式,则 \(dp\) 具有决策单调性。
证明
定义 \(p_i\) 表示 \(i\) 的最优决策点。
\(\forall i\in[1,n],\forall j\in[0,p_i−1]\),根据 \(p_i\) 为 \(i\) 的最优决策点,则有
\(\forall i'\in[i+1,n]\),因为 \(w\) 满足四边形不等式,则有
移项,可得
与第一个不等式相加,可得
即 \(i′\) 的最优决策点一定大于等于 \(p_i\)。故 \(dp\) 具有决策单调性。
决策单调性优化
单调队列
当求出一个新的 \(dp_i\) 时,考虑 \(i\) 可以作为哪些 \(dp_i'\)(\(i′>i\))的最优决策点。根据决策单调性,一定可以找到一个位置,在该位置之前,\(p\) 内存储的决策都比 \(i\) 要优,其后都比 \(i\) 差。于是便可以将该位置及其后面的部分全部变为 \(i\),即此时它们的最优决策均为 \(i\)。
我们可以新建一个队列 \(q\),\(q\) 中每个元素是一个三元组 \(j,l,r\),表示 \(\forall k\)(\(l\le k\le r\)),都有 \(p_k=j\)。
对于每个 \(i\in[1,n]\),执行以下操作
-
检查队首:若队首为 \((j_{head},l_{head},r_{head})\),若 \(r_0<i\) 则删除队首。
-
根据队首的最优决策点 \(j\) 求出 \(dp_i\)
-
加入决策 \(i\):
- 取出队尾:\((j_{tail},l_{tail},r_{tail})\)
- 若对于 \(dp_{l_{tail}}\),\(i\) 是比 \(j_{tail}\) 更优的决策,即 \(dp_i+w(i,l_t)\le f_{j_{tail}}+w(j_t,l_t)\),记 \(pos=l_t\),删除队尾,返回步骤 1。
- 若对于 \(dp_{r_{tail}}\),\(i\) 是比 \(j_{tail}\) 更优的决策,即 \(f_{j_{tail}}+w(j_{tail},r_{tail})\le dp[i]+w(i,r_{tail})\),去往步骤 5。
- 否则,在 \([l_{tail},r_{tail}]\) 二分查找出位置 \(pos\),使得在 \(pos\) 之前的决策都比 \(i\) 优,在 \(pos\) 之后的决策都 \(i\) 更优,将 \([l_{tail},r_{tail}]\) 修改为 \([l_{tail},pos−1]\)。
- 把三元组 \((i,pos,n)\) 插入队尾。
时间复杂度 \(O(n\log n)\)。
分治
假设区间 \([l,r]\) 的最优决策点在区间 \([L,R]\),定义 \(mid=\frac{l+r}{2}\),设 \(dp_{mid}\) 的最优决策点是 \(pos\),由决策单调性可知区间 \([l,mid]\) 的最优决策点在区间 \(l,pos\),区间 \([mid+1,r]\) 的最优决策点在 \([pos,R]\)。
问题分成了子问题,即可分治。
时间复杂度 \(O(n\log n)\)。
P3515 [POI 2011] Lightning Conductor
题意:
给定一个长度为 \(n\) 的序列 \(a\),对于每个 \(i\in[1,n]\),求出一个最小的非负整数 \(p\),使得 \(\forall j\in[1,n]\),都有 \(a_j\le a_i+p−\sqrt{∣i−j∣}\)。
由题意得:
所以:
我们可以做两次 \(dp\),第二次反转序列,可以消除一层绝对值,即 \(p=\lceil\max\limits_{1\le j\le n}\{a_j+\sqrt{i-j}\}\rceil\)。
设 \(dp_i\) 表示 \(\lceil\max\limits_{1\le j\le n}\{a_j+\sqrt{i-j}\}\rceil\),\(w(j,i)\) 即为 \(\sqrt{i-j}\),接下来证明 \(w(j,i)\) 满足四边形不等式。
定义 \(a+1<c\),则:
因为 \(f(x)=\sqrt x-\sqrt{x-1}\) 单调递减,所以上式取值小于 \(0\),所以 \(w(j,i)\) 满足四边形不等式,\(dp\) 具有决策单调性。
代码
单调队列
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 5;
int n, a[N], head, tail;
double dp[N], sq[N];
struct node{
int p, l, r;
}q[N];
double w(int j, int i){
return (double)a[j] + sq[i - j];
}
int search(int p, int i){
int l = q[p].l, r = q[p].r, ans = q[p].r + 1;
int mid;
while (l <= r){
mid = l + r >> 1;
if (w(i, mid) >= w(q[p].p, mid)){
ans = mid, r = mid - 1;
} else {
l = mid + 1;
}
}
return ans;
}
void insert(int i){
q[tail].l = max(q[tail].l, i);
while (head <= tail && w(i, q[tail].l) >= w(q[tail].p, q[tail].l))--tail;
if (head > tail){
q[++tail] = {i, i, n};
} else {
int pos = search(tail, i);
if (pos > n)return;
q[tail].r = pos - 1;
q[++tail] = {i, pos, n};
}
}
void solve(){
head = 1, tail = 0;
for (int i = 1; i <= n; ++i){
insert(i);
if (head <= tail && q[head].r < i)++head;
else q[head].l = i;
dp[i] = max(dp[i], w(q[head].p, i));
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; ++i){
cin >> a[i];
sq[i] = sqrt(i);
}
solve();
for (int i = 1; i <= (n >> 1); ++i){
swap(a[i], a[n - i + 1]), swap(dp[i], dp[n - i + 1]);
}
solve();
for (int i = n; i >= 1; --i){
cout << (int)ceil(dp[i]) - a[i] << "\n";
}
return 0;
}
分治
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 5;
int n, a[N];
double dp[N], sq[N];
double w(int j, int i){
return (double)a[j] + sq[i - j];
}
void solve(int l, int r, int L, int R){
if (l > r)return;
int mid = l + r >> 1, pos;
double maxn = -1;
for (int i = L; i <= min(mid, R); ++i){
if (w(i, mid) > maxn)maxn = w(i, mid), pos = i;
}
dp[mid] = max(dp[mid], maxn);
solve(l, mid - 1, L, pos);
solve(mid + 1, r, pos, R);
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; ++i){
cin >> a[i];
sq[i] = sqrt(i);
}
solve(1, n, 1, n);
for (int i = 1; i <= (n >> 1); ++i){
swap(a[i], a[n - i + 1]);
swap(dp[i], dp[n - i + 1]);
}
solve(1, n, 1, n);
for (int i = n; i >= 1; --i){
cout << (int)ceil(dp[i]) - a[i] << "\n";
}
return 0;
}

浙公网安备 33010602011771号