[NOI 2009]诗人小G

Description

题库链接

给出 $n$ 个字符串 $s_i$,让你把这 $n$ 个字符串顺序不变地分成若干行。每行字符串与字符串之间用空格隔开。假设一行的长度为 $x$,该行的不美观度为 $|x-l|^p$,其中 $l,p$ 给定。让你最小化不美观度和。多测。

$1\leq n\leq 10^5, \max |s_i|\leq 30, l\leq 3\times 10^6, 2\leq p\leq 10$

Solution

设 $f_i$ 表示 $1\sim i$ 的字符串分为若干行后最小的不美观度和。令 $sum_i$ 表示前 $i$ 个字符串并且每个字符串后都加一个空格长度的前缀和。

可以得到转移方程 $f_i=\min\limits_{0\leq j<i}\left{f_j+|sum_i-sum_j-1-l|^p\right}$。

令 $w(j,i)=|sum_i-sum_j-1-l|^p$。我们先证明 $w$ 满足四边形不等式,实际上我们只需要证明 $\forall a<b<c<d$ 都有 $|d-a-l|^p+|c-b-l|^p\geq |d-b-l|^p+|c-a-l|^p$。

证明的话提供一个思路,由于 $y=x^p$ 下凸,并且不等号两边 $d-a+c-b=d-b+c-a$,可以画出这四个区间,并且最长和最短的区间都在不等号左端。用 $l$ 去截四个区间时恒有上式成立。

如果不想感性证明,把上式用函数单调性的思想同样能得出结论。

因此 DP 方程是满足决策单调性的,可以用二分+单调队列来求解。这方面不清楚的可以戳这篇博客学习。

Code

#include <bits/stdc++.h>
#define ld long double
using namespace std;
const int N = 100000+5;

int t, n, L, p, sum[N], pre[N], q[N], head, tail, l[N], r[N], lst[N];
char ch[N][31];
ld f[N];

ld w(int i, int j) {
    ld ans = 1, a = abs(sum[j]-sum[i]-1-L); int b = p;
    while (b) {
        if (b&1) ans = ans*a;
        b >>= 1, a = a*a;
    }
    return ans;
}
void cal() {
    long long ans = 0;
    for (int i = n; i; i = lst[i]) ans += 1ll*w(lst[i], i);
    printf("%lld\n", ans);
}
void print(int x) {
    if (!x) return;
    print(lst[x]);
    for (int i = lst[x]+1; i <= x; i++)
        printf("%s%c", ch[i], " \n"[i == x]);
}
int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d%d", &n, &L, &p);
        for (int i = 1; i <= n; i++) {
            scanf("%s", ch[i]);
            sum[i] = strlen(ch[i])+1+sum[i-1];
        }
        l[head = tail = 0] = 1, r[0] = n;
        for (int i = 1; i <= n; i++) {
            if (r[q[head]] < i) ++head;
            f[i] = f[q[head]]+w(q[head], i);
            lst[i] = q[head];
            if (f[q[tail]]+w(q[tail], n) < f[i]+w(i, n)) continue;
            while (head < tail && f[q[tail]]+w(q[tail], l[q[tail]]) >= f[i]+w(i, l[q[tail]])) --tail;
            int ll = l[q[tail]], rr = n, ans, mid;
            while (ll <= rr) {
                int mid = (ll+rr)>>1;
                if (f[q[tail]]+w(q[tail], mid) >= f[i]+w(i, mid)) rr = mid-1, ans = mid;
                else ll = mid+1;
            }
            r[q[tail]] = ans-1;
            l[q[++tail] = i] = ans, r[q[tail]] = n;
        }
        if (f[n] > 1e18) puts("Too hard to arrange");
        else {
            cal();
            print(n);
        }
        puts("--------------------");
    }
    return 0;
}
posted @ 2020-02-29 18:33  NaVi_Awson  阅读(181)  评论(0编辑  收藏  举报