Description
给出 (n) 个字符串 (s_i),让你把这 (n) 个字符串顺序不变地分成若干行。每行字符串与字符串之间用空格隔开。假设一行的长度为 (x),该行的不美观度为 (|x-l|^p),其中 (l,p) 给定。让你最小化不美观度和。多测。
(1leq nleq 10^5, max |s_i|leq 30, lleq 3 imes 10^6, 2leq pleq 10)
Solution
设 (f_i) 表示 (1sim i) 的字符串分为若干行后最小的不美观度和。令 (sum_i) 表示前 (i) 个字符串并且每个字符串后都加一个空格长度的前缀和。
可以得到转移方程 (f_i=minlimits_{0leq j<i}left{f_j+|sum_i-sum_j-1-l|^p ight})。
令 (w(j,i)=|sum_i-sum_j-1-l|^p)。我们先证明 (w) 满足四边形不等式,实际上我们只需要证明 (forall a<b<c<d) 都有 (|d-a-l|^p+|c-b-l|^pgeq |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
", 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], "
"[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;
}