题目链接:P1912 [NOI2009] 诗人小G
题目概述
给你几个字符串,你可以按照给定的顺序任意拼接(你可以分组),但是拼接的时候中间要打空格,设这个当前的拼接长度为 \(sum\),那么代价为 \(|sum-L|^P\),求最小的代价并输出方案。
分析
Luogu题解告诉我们一个快速判断决策单调性的办法:https://www.luogu.com.cn/article/38xahpje。
首先能够决策单调性优化的 \(dp\) 是:\(f_i=\min f_j+w(j,i)\),最优决策点为 \(p_i\),若都满足 \(p_i\leq p_{i+1}\) 那么就满足决策单调性。
设决策点为 \(j\) 的函数 \(f_j(x)=f_j+w(j,x)\),假设两个决策点 \(j<l\),如果 \(f_j(x)\) 与 \(f_l(x)\) 有且仅有一个交点,那么其满足决策单调性。
步入正题。
首先显然:
我们感性理解一下就知道这满足决策单调性(你也可以打表证明)。
虽然说这道题目是一道经典题,我们还是要记录许多 trick 的。
传统的决策单调性二分队列写法十分的骚气。
按照以下过程执行:
队列中每一个值存三个状态 \((k,l,r)\),代表决策点 \(k\) 在 \([l,r]\) 有效。
对于队头中 \(r\geq i\) 的弹出。
然后用队头进行转移。
对于当前决策点 \(i\),如果后面队尾的 \(l\) 决策都比我菜,那你也没有存在的必要了。如果只是 \(r\) 比我菜,那么我们二分这个然后分别赋值就可以了。
我们发现有些 \(l,r\) 是不是没有必要,我们只需要维护相邻两个决策点的临界值即可。
对于此题,我们有可能爆 long long
,可以通过精度(long double
)来换取。
代码
时间复杂度 \(\mathcal{O}(n\log n\log P)\)。
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#define int long long
#define ld long double
#define N 100005
using namespace std;
#define abs(x) ((x) < 0 ? -(x) : (x))
#define isdigit(ch) ('0' <= ch && ch <= '9')
template<typename T>
void read(T&x) {x = 0;char ch = getchar();for (;!isdigit(ch);ch = getchar());for (;isdigit(ch);ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
}
template<typename T>
void write(T x) {if (x > 9) write(x / 10);putchar(x % 10 + '0');
}
ld qpow(ld a,int b) {ld res = 1;while(b) {if (b & 1) res *= a;a *= a;b >>= 1;}return res;
}
int T,len[N],n,P,L,sum[N],q[N],p[N],pre[N];
ld f[N];
string s[N];
ld calc(int i,int j) {return f[j] + qpow(abs(sum[i] - sum[j] - L),P);
}
int find(int x,int y) {int l = x,r = n + 1,res = n + 1;while (l <= r) {int mid = l + r >> 1;if (calc(mid,x) >= calc(mid,y)) res = mid,r = mid - 1;else l = mid + 1;}return res;
}
signed main(){read(T);for (;T --;) {read(n),read(L),read(P);L ++;for (int i = 1;i <= n;i ++) {cin >> s[i];len[i] = s[i].size();}for (int i = 1;i <= n;i ++) sum[i] = sum[i - 1] + len[i] + 1,f[i] = 1e18 + 5;f[0] = 0;int head = 1,tail = 0;q[++tail] = 0;for (int i = 1;i <= n;i ++) {while(head < tail && p[head] <= i) head ++;f[i] = calc(i,q[head]);pre[i] = q[head];while(head < tail && p[tail - 1] >= find(q[tail],i)) tail --;p[tail] = find(q[tail],i);q[++tail] = i;}if (f[n] > 1e18) puts("Too hard to arrange");else {write((int)(f[n] + 0.5)),putchar('\n');int now = n;vector<string> ls;while(now) {ls.push_back("");for (int i = now;i > pre[now];i --)ls.push_back(s[i]);now = pre[now];}reverse(ls.begin(),ls.end());bool flag = 0;for (auto i : ls) {if (i == "") putchar('\n'),flag = 0;else {if (flag) putchar(' ');cout << i;flag = 1;}}}puts("--------------------");}return 0;
}