题意
有n个字符串,将其按顺序分组,每组的代价是总长度与常数L的差的绝对值的P次方,要使所有组的代价之和最小
即
a n s = ∑ k ( ∑ i ∈ g r o u p k l e n [ i ] ) P ans=\sum_{k}(\sum_{i \in group_k}len[i])^P ans=k∑(i∈groupk∑len[i])P
最小化 a n s ans ans。
思路
首先我们考虑暴力。
令 s u m [ i ] = ∑ j = 1 i l e n [ j ] sum[i]=\sum_{j=1}^{i}len[j] sum[i]=∑j=1ilen[j] , f [ i ] f[i] f[i]表示前i个字符串分组的最小价值。
那么
f [ i ] = m i n j = 0 j < i ( f [ j ] + ∣ s u m [ i ] − s u m [ j ] − L ∣ P ) f[i]=min_{j=0}^{j < i}(f[j]+|sum[i]-sum[j]-L|^P) f[i]=minj=0j<i(f[j]+∣sum[i]−sum[j]−L∣P)
然后我们来优化这个 O ( n 2 ) O(n^2) O(n2)的DP。
我们可以发现 y = ∣ k 1 − x ∣ P + k 2 y=|k_1-x|^P+k_2 y=∣k1−x∣P+k2形如( k 1 , k 2 k_1,k_2 k1,k2是常数)(P=1的时候变成直线就好了):
假如 k 1 = s u m [ j ] − L , k 2 = f [ j ] , x = s u m [ i ] k_1=sum[j]-L,k_2=f[j],x=sum[i] k1=sum[j]−L,k2=f[j],x=sum[i],那么画出一堆不同的 s u m [ j ] sum[j] sum[j]对应的函数应该是这样的:
由于我们要最小化
f
[
i
]
f[i]
f[i],所以我们将会取
x
=
s
u
m
[
i
]
x=sum[i]
x=sum[i]时函数值最小的那个。
直接观察发现,因为两个函数的形状是一样的,不同之处只在于位置,那么两个函数肯定是在某一点相交,然后在这个店左侧一个函数值小,右侧另一个小。
或者这么说,这些长得像二次函数的函数满足斜率递增(导函数单调递增我并不会求导所以不想这么说)。所以
x
x
x相同时,不论
x
x
x为何值,两个函数斜率的大小关系是固定的,通俗地说就是一个函数一直增长得比另一个快。
所以我们可以用决策单调性来优化这个DP。
在单调队列里我们存下一些函数,然后存下他到什么时候会被下一个函数超过从而再也没有翻身的机会。
假如当 x = s u m [ i ] x=sum[i] x=sum[i]已经大于队头被超过的位置了,那么 h e a d + + head++ head++。最后用队头更新 f [ i ] f[i] f[i]
假如新加的函数超过 q u e [ t a i l ] que[tail] que[tail]函数的位置要小于 q u e [ t a i l ] que[tail] que[tail]超过 q u e [ t a i l ] − 1 que[tail]-1 que[tail]−1的位置,那么 q u e [ t a i l ] que[tail] que[tail]也是没用的,因为函数将会长成这个样子,那么蓝色的就是被丢掉的 q u e [ t a i l ] que[tail] que[tail]
注意
1.果然还是画图比较好理解,当时做的时候想得死去活来。。。
2.这道题虽然限制了 a n s > 1 0 18 ans > 10^{18} ans>1018就不用再做,但是要是把大于 1 0 18 10^{18} 1018的数都设成一个常数 i n f inf inf的话是会出锅的,因为在找函数交点的时候不管用 > > >还是 ≥ \ge ≥都会难以判断一些情况,在这里不再赘述。所以暴力开 l o n g    d o u b l e long \; double longdouble就好了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long double LD;
typedef long long LL;
const LL inf = 1e18+1000;
const int N = 1e5+10, L = 40;
int T, n, l, p, len[N];
char s[N][L];
LD f[N];
int pre[N], que[N], h, t, bey[N];
inline LD fpow(LD x, int y){
LD ret = 1;
while (y){
if (y&1) ret *= x;
x = x*x;
y >>= 1;
}
return ret;
}
LD get_val(int leni, int j)
{
return f[j]+fpow(abs(leni-len[j]-l-1), p);
}
int get_bey(int j, int k)
{
int l = 1, r = len[n], mid, ret = r+1;
while (l <= r){
mid = (l+r)>>1;
if (get_val(mid, k) <= get_val(mid, j))
ret = mid, r = mid-1;
else l = mid+1;
}
return ret;
}
void print_ans(int now)
{
if (now == 0) return;
print_ans(pre[now]);
for (int i = pre[now]+1; i <= now; ++ i){
printf("%s", s[i]);
if (i != now) putchar(' ');
}
puts("");
}
int main()
{
scanf("%d", &T);
for (; T--; ){
scanf("%d%d%d", &n, &l, &p);
for (int i = 1; i <= n; ++ i)
scanf("%s", s[i]), len[i] = len[i-1]+strlen(s[i])+1;
que[h = t = 1] = 0;
memset(bey, 0, sizeof(bey));
for (int i = 1; i <= n; ++ i){
while (h < t && bey[h] <= len[i]) ++h;
pre[i] = que[h]; f[i] = get_val(len[i], que[h]);
while (h < t && get_val(bey[t-1], i) <= get_val(bey[t-1], que[t])) --t;
bey[t] = get_bey(que[t], i); que[++t] = i;
}
if (f[n] > 1e18) puts("Too hard to arrange");
else{
printf("%lld\n", (LL)(f[n]+0.5));
print_ans(n);
}
for (int i = 1; i <= 20; ++ i) putchar(45);
puts("");
}
return 0;
}