把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

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)\) 有且仅有一个交点,那么其满足决策单调性。

步入正题。

首先显然:

\[f_i=\min_{j\in[0,i-1]}f_j+|sum_i-sum_j-1-L|^P \]

我们感性理解一下就知道这满足决策单调性(你也可以打表证明)。

虽然说这道题目是一道经典题,我们还是要记录许多 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;
}
posted @ 2025-10-15 22:18  high_skyy  阅读(8)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end