P1912 [NOI2009] 诗人小G 分析
题目链接: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;
}

浙公网安备 33010602011771号