juruohjrblog喵~

决策单调性优化入门

juruohjr·2022-01-11 21:37·47 次阅读

决策单调性优化入门

前置知识#

先学下面的东西#

  • 单调队列

四边形不等式#

定义#

对于一个二元函数 w(l,r)w(l,r),如果有下面的命题成立,则称 ww 满足四边形不等式:

ab<cd, w(a,c)+w(b,d)w(a,d)+w(b,c)\forall a \leq b < c \leq d,~ w(a,c)+w(b,d) \leq w(a,d) + w(b,c)

定理#

如果二元函数 w(a,b)w(a,b) 满足 w(a+1,b+1)+w(a,b)w(a,b+1)+w(a+1,b)w(a+1,b+1)+w(a,b) \leq w(a,b+1)+w(a+1,b) ,那么 w(a,b)w(a,b) 满足四边形不等式。

证明

  • 对于 a<ba<b, 有 w(a,b)+w(a+1,b+1)w(a,b+1)+w(a+1,b)w(a,b)+w(a+1,b+1)\leq w(a,b+1) + w(a+1,b)
  • 对于 a+1<ba+1<b,有 w(a+1,b)+w(a+2,b+1)w(a+1,b+1)+w(a+2,b)w(a+1,b)+w(a+2,b+1)\leq w(a+1,b+1) + w(a+2,b)

将两式相加,然后化简:

w(a,b)+w(a+1,b+1)+w(a+1,b)+w(a+2,b+1)w(a,b+1)+w(a+1,b)+w(a+1,b+1)+w(a+2,b)w(a,b)+w(a+2,b+1)w(a,b+1)+w(a+2,b)\begin{aligned} w(a,b)+w(a+1,b+1)+w(a+1,b)+w(a+2,b+1)&\leq w(a,b+1)+w(a+1,b)+w(a+1,b+1)+w(a+2,b)\\ w(a,b)+w(a+2,b+1)&\leq w(a,b+1)+w(a+2,b) \end{aligned}

以此类推,可以得到:

ab<c,w(a,c)+w(b,c+1)w(a,c+1)+w(b,c)\forall a \leq b < c, w(a,c)+w(b,c+1) \leq w(a,c+1)+w(b,c)

可以对第二个数做类似的操作,可以得到:

ab<cd, w(a,c)+w(b,d)w(a,d)+w(b,c)\forall a \leq b < c \leq d,~ w(a,c)+w(b,d) \leq w(a,d) + w(b,c)

Q.E.D\rm Q.E.D

正文#

定义#

考虑一个形如下面式子的 dp\rm dp 转移:

f(i)=min1j<i{f(j)+w(j,i)}f(i)=\min_{1\leq j< i} \{f(j)+w(j,i) \}

p(i)p(i) 表示 f(i)f(i) 的最优决策点,即:

p(i)=argmin1j<i{f(j)+w(j,i)}p(i)=\arg\min_{1\leq j < i} \{f(j)+w(j,i) \}

而这个 dp\rm dp 有决策单调性,当且仅当 p(i)p(i) 单调不降。

定理#

转移参数 w(j,i)w(j,i) 满足四边形不等式 \Rightarrow f(i)f(i) 满足决策单调性。

证明

a<b<i<j\forall a < b < i < j, 设 p(i)=bp(i) = b,则有 f(b)+w(b,i)f(a)+w(a,i)f(b)+w(b,i)\leq f(a)+w(a,i)

根据四边形不等式,有

w(a,i)+w(b,j)w(a,j)+w(b,i)w(a,i)+w(b,j) \leq w(a,j)+w(b,i)

将两条式子相加,然后化简,得到:

f(b)+w(b,j)f(a)+w(a,j)f(b)+w(b,j)\leq f(a)+w(a,j)

由此可以看出,对于 ii 之后的所有状态,其最优决策点都可以不是 aa ,因此可以有 p(j)bp(j)\ge b ,因此满足决策单调性。

碎碎念#

QQ:上面的内容似乎只针对 min\min ,那么对于 max\max 应该怎么办呢?

AA:把四边形不等式的不等号取反即可。

决策单调性和斜率优化关联性挺大的,但是本 juruo\rm juruo 觉得两个东西本质上有点不同。

应用#

[NOI 2009] 诗人小G#

\Rightarrow 原题链接

可以得到转移:

f(i)=min0j<i{f(j)+sisj+(ij1)LP}f(i)=\min_{0\leq j < i} \left\{f(j)+\left|s_i-s_j+(i-j-1)-L\right|^P\right\}

其中 si=si1+stris_i = s_{i-1}+|\mathrm{str}_i|

w(i,j)=sisj+(ij1)LPw(i,j) = \left|s_i-s_j+(i-j-1)-L\right|^P,显然当前就是要证明这个东西满足四边形不等式。

根据四边形不等式的性质,可以转化为证明

w(i+1,j+1)+w(i,j)w(i,j+1)+w(i+1,j)w(i+1,j+1)+w(i,j) \leq w(i,j+1)+w(i+1,j)

这个证明不太显然,需要稍微搞一搞。教给各位 dalao\rm dalao 了。这里主要介绍知道决策单调性之后的做法。

显然,对于每个决策点 xx,都有一个贡献区间 [lx,rx](k[lx,rx],p(k)=x)[l_x, r_x] (\forall k \in [l_x, r_x], p(k)=x) ,可以用二分求出对于决策点 xx,第一个比决策点 yy 优的位置,这个明显可以使用二分求出,设其为 g(x,y)g(x,y)

接着用单调队列维护出 ll 单调递增的决策点序列。

显然,对于队列中相邻的两个决策 x,y (x<y)x,y~(x<y),显然是 rx=g(y,x)1r_x = g(y,x)-1

  • 当队头的 rr 小于 ii 的时候就推出。
  • 考虑加入一个新的决策点 pp, 设这时候队尾的两个元素分别为 x,y (x<y)x,y~(x<y),显然,如果 g(y,x)g(p,y)g(y,x) \ge g(p, y) ,那么显然,将 yy 弹出,直到队列长度只有 11 ,或者 g(y,x)<g(p,y)g(y,x) < g(p,y) ,再压入 pp

为了优化常数,可以在队列中记录 rxr_x

Copy
#include <bits/stdc++.h> using namespace std; typedef long double LL; const int maxn = 1e5 + 5; const LL INF = 1e18; int n, P, q[maxn], ql, qr; LL L, s[maxn], fr[maxn], f[maxn]; string str[maxn]; LL qpow(LL a, int b) { LL ret = 1; while (b) { if (b & 1) ret = ret * a; a = a * a, b >>= 1; } return ret; } inline LL calc(int x, int y) { return f[x] + qpow(abs(s[y] - s[x] + (y - x - 1) - L), P); } int bsearch(int x, int y) { if (calc(x, n) < calc(y, n)) return n + 1; int l = y, r = n, ans = -1; while (l <= r) { int mid = (l + r) >> 1; if (calc(y, mid) <= calc(x, mid)) ans = mid, r = mid - 1; else l = mid + 1; } return ans; } void print_plan(int x) { if (!x) return ; print_plan(fr[x]); for (int i = fr[x] + 1; i < x; i++) cout << str[i], putchar(' '); cout << str[x]; putchar('\n'); } void solve() { scanf("%d%Lf%d", &n, &L, &P); memset(q, 0, sizeof(q)), fill(f, f + n + 1, INF); for (int i = 1; i <= n; i++) cin >> str[i], s[i] = s[i - 1] + str[i].size(); f[0] = q[ql = qr = 1] = 0; for (int i = 1; i <= n; i++) { while (ql < qr && bsearch(q[ql], q[ql + 1]) <= i) ql++; f[i] = calc(q[ql], i), fr[i] = q[ql]; while (ql < qr && bsearch(q[qr - 1], q[qr]) >= bsearch(q[qr], i)) qr--; q[++qr] = i; } if (f[n] > INF) { printf("Too hard to arrange\n--------------------\n"); return ; } printf("%.0Lf\n", f[n]), print_plan(n); printf("--------------------\n"); } int main() { int T; scanf("%d", &T); while (T--) solve(); return 0; }

[UOJ 285] 数据分块鸡#

\Rightarrow 原题链接

和上一题的转移方程非常类似,只不过 w(i,j)w(i,j) 的计算方法不同,需要使用数据结构优化,就不讲了。

[CF-868F] Yet Another Minimization Problem#

\Rightarrow luogu\rm luogu 链接

题意非常清楚明白,可以直接设出 f(i,j)f(i,j) 表示将前 jj 个元素分成 ii 段的最小权值,可以得到转移:

f(i,j)=min0k<j{f(i1,k)+w(k+1,j)}f(i,j) = \min_{0 \leq k < j} \{f(i-1,k)+w(k+1,j) \}

其中 w(i,j)w(i,j) 表示区间 [l,r][l,r] 中重复元素的对数,可以看出这个函数满足四边形不等式,但是这个函数的求值时间复杂度是 O(n)O(n) 的,因此不能使用先前的单调队列优化了。这里提出另一种优化决策单调性的方法:分治

这种方法适用于 dp\rm dp 的转移不依赖同一维状态的转移,比如这道题的转移 f(i,)f(i,*) 依赖于 f(i1,)f(i-1,*) ,但是 f(i,)f(i,*) 的状态之间不存在依赖关系。这个也可以计算形如下面的 "dp\rm dp"。:

f(i)=min0j<i{w(j,i)}f(i) = \min_{0\leq j < i}\{w(j,i)\}

设立一个函数 calc(l,r,x,y)\mathrm{calc}(l, r, x, y) 用于计算状态在 [l,r][l,r] 之间的所有 ff 值,其中决策点只能在 [x,y][x,y] 中找。

分为下面的步骤:

  • m=l+r2m = \left\lfloor \frac{l+r}{2}\right\rfloor
  • 计算出 f(m)f(m) ,同时得到其最优决策点 p(m)p(m)
  • 分治 calc(l,m1,x,p(m)),calc(m+1,r,p(m),y)\mathrm{calc}(l, m - 1, x, p(m)), \mathrm{calc}(m + 1,r,p(m), y)

如果 w(i,j)w(i,j) 的求值时间复杂度为 O(1)O(1) 的,这个分治过程显然是 O(nlog2n)O(n\log_2 n) 的。

但关键是现在 w(i,j)w(i,j) 的计算是 O(n)O(n) 的。

这里可以采用类似莫队的双指针移动,维护 w(i,j)w(i,j) 。首先可以想到将两个指针的移动分开计算复杂度。然后就会发现两个指针移动的次数都是 O(nlog2n)O(n\log_2n) 的。

因此一次分治的时间复杂度为 O(nlog2n)O(n\log_2 n) ,总的时间复杂度为 O(knlog2n)O(kn \log_2 n)

Copy
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int maxn = 1e5 + 5, maxk = 25; const LL INF = 1e18; int n, m, a[maxn]; int cl, cr; LL f[maxk][maxn], cnt[maxn], cw; inline void upd_cnt(int col, int vl) { cw -= cnt[col] * (cnt[col] - 1) / 2; cnt[col] += vl; cw += cnt[col] * (cnt[col] - 1) / 2; } LL calc_w(int l, int r) { while (cl < l) upd_cnt(a[cl], -1), cl++; while (cl > l) cl--, upd_cnt(a[cl], 1); while (cr > r) upd_cnt(a[cr], -1), cr--; while (cr < r) cr++, upd_cnt(a[cr], 1); return cw; } int cur_m; void solve(int l, int r, int fl, int fr) { if (fl > fr || l > r) return ; int pos = 0; LL res = INF; int mid = (l + r) >> 1, rgl = max(1, fl), rgr = min(mid, fr); for (int i = rgl; i <= rgr; i++) { LL vl = f[cur_m - 1][i - 1] + calc_w(i, mid); if (vl < res) res = vl, pos = i; } f[cur_m][mid] = res; solve(l, mid - 1, fl, pos), solve(mid + 1, r, pos, fr); } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); cnt[a[1]] = 1, cw = 0, cl = cr = 1; f[0][0] = 0; for (int i = 1; i <= n; i++) f[0][i] = INF; for (cur_m = 1; cur_m <= m; cur_m++) solve(1, n, 1, n); printf("%lld\n", f[m][n]); return 0; }

[POI2011]Lightning Conductor#

\Rightarrow luogu\rm luogu 链接

这题两种方法都可以,不讲了。

posted @   juruohjr  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示
目录