【DP】NOI 2009 诗人小G

时间:2022-12-16 15:08:41

Description

    小G是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
    一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小G给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小G不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小G对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的P次方,而一个排版的不协调度为所有行不协调度的总和。
    小G最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。

Input

    本题中包含多组测试数据。
    输入文件中的第一行为一个整数T,表示诗的数量。
    接下来为T首诗,这里一首诗即为一组测试数据。每组测试数据中的第一行为三个由空格分隔的正整数N,L,P,其中:N表示这首诗句子的数目,L表示这首诗的行标准长度,P的含义见问题描述。
    从第二行开始,每行为一个句子,句子由英文字母、数字、标点符号等符号组成(ASCII码33~127,但不包含’-‘)。

Output

    对于每组测试数据,若最小的不协调度不超过10^18,则第一行为一个数,表示不协调度。接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。
    如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。若最小的不协调度超过10^18,则输出“Too hard to arrange”(不含引号)。每组测试数据结束后输出“——————–”(不含引号),共20个“-”,“-”的ASCII码为45,请勿输出多余的空行或者空格。
    由于缺少special judge,因此在这里只要求输出最小的不协调度。格式不变,依然以”-“分割。

Sample Input

4
4 9 3
brysj,
hhrhl.
yqqlm,
gsycl.
4 9 2
brysj,
hhrhl.
yqqlm,
gsycl.
1 1005 6
poet
1 1004 6
poet

Sample Output

108
--------------------
32
--------------------
Too hard to arrange
--------------------
1000000000000000000
--------------------

    前两组输入数据中每行的实际长度均为6,后两组输入数据每行的实际长度均为4。一个排版方案中每行相邻两个句子之间的空格也算在这行的长度中(可参见样例中第二组数据)。每行末尾没有空格。

Data

测试点 T N L P
1 <=10 <=18 <=100 <=5
2 <=10 <=2000 <=60000 <=10
3 <=10 <=2000 <=60000 <=10
4 <=5 <=100000 <=200 <=10
5 <=5 <=100000 <=200 <=10
6 <=5 <=100000 <=3000000 2
7 <=5 <=100000 <=3000000 2
8 <=5 <=100000 <=3000000 <=10
9 <=5 <=100000 <=3000000 <=10
10 <=5 <=100000 <=3000000 <=10

I think

    DP+决策单调性
    用f[i]表示排列完前i个句子得到的最小不协调度,则有转移方程:

f[i]=min{f[j]+|sum[i]sum[j]+(ij1)L|p}

    若使用朴素算法,则复杂度为 O(n2) ,过前三个点。可以证明此方程满足决策单调性,因此用单调栈优化。具体单调性证明戳这里。 https://www.byvoid.com/zhs/blog/noi-2009-poet
Attention:1.保证精度打long double
                 2.二分要好好体会

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int sm = 1e5+5;
const long long lim = 1e18;

typedef long double ld;

int N,T,P,L;
int tl;
int g[sm],stk[sm];//stk[]记可用于更新后面f[j]的f[i]的编号i
ld ans,f[sm],s[sm];//g[stk[i]]记f[stk[i]]能够更新的第一句诗对应编号
char ss[50];

int get_len() {scanf("%s",ss);return strlen(ss);}
int get_p(int x) {//找第一个g[stk[]]
    int l=1,r=tl,mid;
    while(l<r) {
        mid=(l+r+1)>>1;
        if(g[stk[mid]]<=x)l=mid;
        else r=mid-1;
    }
    return l;
}
ld Pow(ld x,int y) {ans=1;while(y){if(y&1)ans*=x;y>>=1;x*=x;}return ans<0?-ans:ans;}
ld F(int i,int j) { return f[i]+Pow(s[j]-s[i]+(j-i-1)-L,P);}
int get_g(int x) {
    int l=g[stk[tl]]+1,r=N+1,mid;
    while(l<r) {//找到x可更新的
        mid=(l+r)>>1;
        if(F(stk[tl],mid)>F(x,mid))r=mid;//作为mid的转移项,x比stk[tl]更优
        else l=mid+1;
    }
    return l;
}

int main() {
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d\n",&N,&L,&P);
        for(int i=1;i<=N;++i)s[i]=s[i-1]+get_len();
        tl=1;g[0]=1;stk[1]=0;
        for(int i=1;i<=N;++i) {
            int p=stk[get_p(i)];
            f[i]=F(p,i);
            while(i<g[stk[tl]]&&F(i,g[stk[tl]])<F(stk[tl],g[stk[tl]]))--tl;
            p=get_g(i);
            if(p!=N+1)
                stk[++tl]=i,g[stk[tl]]=p;
        }
        if(f[N]>lim)puts("Too hard to arrange");
        else printf("%.0Lf\n",f[N]);
        puts("--------------------");
    }
    return 0;
}