zoukankan      html  css  js  c++  java
  • 决策单调性优化入门

    前置知识

    先学下面的东西

    • 单调队列

    四边形不等式

    定义

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

    \[\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+1,b+1)+w(a,b) \leq w(a,b+1)+w(a+1,b)\) ,那么 \(w(a,b)\) 满足四边形不等式。

    证明

    • 对于 \(a<b\), 有 \(w(a,b)+w(a+1,b+1)\leq w(a,b+1) + w(a+1,b)\)
    • 对于 \(a+1<b\),有 \(w(a+1,b)+w(a+2,b+1)\leq w(a+1,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} \]

    以此类推,可以得到:

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

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

    \[\forall a \leq b < c \leq d,~ w(a,c)+w(b,d) \leq w(a,d) + w(b,c) \]

    \(\rm Q.E.D\)

    正文

    定义

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

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

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

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

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

    定理

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

    证明

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

    根据四边形不等式,有

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

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

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

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

    碎碎念

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

    \(A\):把四边形不等式的不等号取反即可。

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

    应用

    [NOI 2009] 诗人小G

    \(\Rightarrow\) 原题链接

    可以得到转移:

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

    其中 \(s_i = s_{i-1}+|\mathrm{str}_i|\)

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

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

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

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

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

    接着用单调队列维护出 \(l\) 单调递增的决策点序列。

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

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

    为了优化常数,可以在队列中记录 \(r_x\)

    #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)\) 的计算方法不同,需要使用数据结构优化,就不讲了。

    [CF-868F] Yet Another Minimization Problem

    \(\Rightarrow\) \(\rm luogu\) 链接

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

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

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

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

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

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

    分为下面的步骤:

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

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

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

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

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

    #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\) \(\rm luogu\) 链接

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

  • 相关阅读:
    对象池使用时要注意几点
    Flash3D学习计划(一)——3D渲染的一般管线流程
    714. Best Time to Buy and Sell Stock with Transaction Fee
    712. Minimum ASCII Delete Sum for Two Strings
    647. Palindromic Substrings(马拉车算法)
    413. Arithmetic Slices
    877. Stone Game
    338. Counting Bits
    303. Range Sum Query
    198. House Robber
  • 原文地址:https://www.cnblogs.com/juruohjr/p/15780634.html
Copyright © 2011-2022 走看看