「Bzoj 1563」「NOI2009」诗人小G (DP + 决策单调性 + 四边形不等式)

BZOJ 1563
题意:有一个长度为 $n$ 的序列 $A$ 和常数 $L,P$ ,你需要将它分成若干段,每一段的代价为 $|\sum A_i−L|^P$ ,求最小代价的划分方案。

易得暴力DP
$$
dp(i) = \min_{j = 0} ^ {i - 1} (|\text{sum}(i) - \text{sum}(j) - L|^P + dp(j))
$$

发现这个$dp$满足决策单调性,即如果我们把每个位置$i$由哪个位置转移而来记为$g(i)$,则$g(i)$为单调不减的

这个可以数学证明也可以打表验证

然后我们就可以通过一个队列来完成优化,即我们队列存的是三元组$(l,r,j)$表示$[l,r]$区间的$dp$值的最优决策为$j$

那么我们每次在队头删掉$r$小于当前枚举的$i$的三元组
然后就就可以取队头元素来计数$dp(i)$
然后我们在队尾处理,如果当前有决策$i$, 队尾三元组$(l_t,r_t,j_t)$,则如果$\text{val}(i, l_t) \leq \text{val}(l_j, l_t)$,那么删掉队尾三元组
然后我们尝试插入当前的$i$,我们在当前$[l_t,r_t]$中二分一个位置$pos$,使得$j_t$是$[l_t,pos-1]$的最优决策,$i$是$[pos, r_t]$的最优决策

最后注意开 long double 来判断是否大于$10^{18}$

知识点
打表验证决策单调性

#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<queue>
#include<string>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long double
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;

namespace flyinthesky {

    struct node {int l, r, j;} q[100000 + 5];
    int n, L, P, fr[100000 + 5], l, r, stk[100000 + 5], top;
    LL dp[100000 + 5], sum[100000 + 5];
    char s[100000 + 5][35];

    LL calc(int j, int i) {
        LL gg = sum[i] - sum[j] - L + i - j - 1, ret = 1;
        if (gg < 0) gg = -gg;
        for (int i = 1; i <= P; ++i) ret *= gg;
        return dp[j] + ret;
    }
    int find(node a, int y) {
        int l = a.l, r = a.r;
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (calc(a.j, mid) < calc(y, mid)) l = mid + 1; else r = mid - 1;
        }
        return l;
    }

    void clean() {
    }
    int solve() {

        clean();
        cin >> n >> L >> P;
        for (int i = 1; i <= n; ++i) scanf("%s", s[i]), sum[i] = sum[i - 1] + strlen(s[i]);

        q[l = r = 1] = (node){1, n, 0};

        for (int i = 1; i <= n; ++i) {
            while (l < r && q[l].r < i) ++l;
            dp[i] = calc(q[l].j, i), fr[i] = q[l].j;
            while (l <= r && calc(i, q[r].l) <= calc(q[r].j, q[r].l)) --r;
            if (l > r) q[++r] = (node){i, n, i};
            else if (calc(i, n) <= calc(q[r].j, n)) { // 二分找得到这样的 pos 吗?
                int pos = find(q[r], i);
                q[r].r = pos - 1, q[++r] = (node){pos, n, i};
            }
        }

        if (dp[n] > 1e18) puts("Too hard to arrange");
        else {
            printf("%lld\n", (long long)dp[n]);

            int now = n;
            top = 0;
            while (now) stk[++top] = now, now = fr[now];
            for (int lst = 0, i = top; i; --i) {
                for (int j = lst + 1; j <= stk[i]; ++j) printf("%s%c", s[j], j == stk[i] ? '\n' : ' ');
                lst = stk[i];
            }
        }

        puts("--------------------");

        return 0;
    }  
}
int main() {
    int T; cin >> T;
    while (T--) flyinthesky::solve();
    return 0;
}
/*
*/
------ 本文结束 ------