304. 诗人小G

题目链接

304. 诗人小G

G 是一个出色的诗人,经常作诗自娱自乐。

但是,他一直被一件事情所困扰,那就是诗的排版问题。

一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。

G 给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。

显然排版时,不应改变原有的句子顺序,并且小 G 不允许把一个句子分在两行或者更多的行内。

在满足上面两个条件的情况下,小 G 对于排版中的每行定义了一个不协调度,为这行的实际长度与行标准长度差值绝对值的 P 次方,而一个排版的不协调度为所有行不协调度的总和。

G 最近又作了几首诗,现在请你对这几首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。

输入格式

第一行包含一个整数 T,表示诗的数量,接下来是 T 首诗,每首诗是一组数据。

每组数据的第一行包含三个整数 NLP,其中 N 表示这首诗句子的数目,L 表示这首诗的行标准长度,P 的含义参考问题描述。

从第二行开始,每行一个句子,句子由英文字母、数字、标点符号等符号组成(ASCII 码 33127,但不包含 -)。

输出格式

对于每组测试数据,若最小的不协调度不超过 1018,则第一行为一个数,表示不协调度。接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。

如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。(本题有 special judge)(由于本题数据量大,展示标准答案时,不展示可行解)

若最小的不协调度超过 1018,则输出 Too hard to arrange

每组测试数据结束后输出 --------------------,共 20-- 的 ASCII 码为 45,请勿输出多余的空行或者空格。

数据范围

总共 10 个测试点,数据范围满足:

测试点 T N L P
1 10 18 100 5
2 10 2×103 6×104 10
3 10 2×103 6×104 10
4 5 105 200 10
5 5 105 200 10
6 5 105 3×106 2
7 5 105 3×106 2
8 5 105 3×106 10
9 5 105 3×106 10
10 5 105 3×106 10

所有测试点中均满足句子长度不超过 30P1

输入样例:

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

输出样例:

108 brysj, hhrhl. yqqlm, gsycl. -------------------- 32 brysj, hhrhl. yqqlm, gsycl. -------------------- Too hard to arrange -------------------- 1000000000000000000 poet --------------------

样例解释

前两组输入数据中每行的实际长度均为 6,后两组输入数据每行的实际长度均为 4

一个排版方案中每行相邻两个句子之间的空格也算在这行的长度中(可参见样例中第二组数据)。

每行末尾没有空格。

解题思路

四边形不等式优化dp

四边形不等式:对于一个二元函数 w(x,y),对于定义域上的任何四个整个整数 abcd,都有 w(a,d)+w(b,c)w(a,c)+w(b,d),则称函数 w 满足四边形不等式
四边形不等式的另外一个等价定义:对于定义域上的任何两个整数 a<b,都有 w(a,b+1)+w(a+1,b)w(a,b)+w(a+1,b+1)
证明:
对于 a<c,则 a<a+1c<c+1,有 w(a,c+1)+w(a+1,c)w(a,c)+w(a+1,c+1)
对于 a+1<c,则 a+1<a+2c<c+1,有 w(a+1,c+1)+w(a+2,c)w(a+1,c)+w(a+2,c+1)
两式相加,得 w(a,c+1)+w(a+2,c)w(a,c)+w(a+2,c+1)

得对于 a<b<c<c+1w(a,c+1)+w(b,c)w(a,c)+w(b,c+1)
同理,对于 a<b<c+1,则 a<b<c+1<c+2,有 w(a,c+2)+w(b,c+1)w(a,c+1)+w(b,c+2)
两式相加,得 w(b,c)+w(a,c+2)w(a,c)+w(b,c+2)

得对于 a<b<c<dw(b,c)+w(a,d)w(a,c)+w(b,d)
显然,a=b=c=d 时该式依然成立,故有 abcdw(a,d)+w(b,c)w(a,c)+w(b,d),得证
一般证明四边形不等式都是拿该等价定义来证明
一维线性dp的不等式优化
对于形如 f[i]=min0j<i{f[j]+val(i,j)},如果 val(i,j) 这个函数满足四边形不等式,则一般可以将一维优化为 O(logn)

结论:假设 p[i]i 的最优决策,如果 val(i,j) 满足四边形不等式,则 f[i] 具有决策单调性,即对于遍历 i 的任何时候,如果 ab,则有 p[a]p[b]对于 i[1,n],j[0,p[i]],都有 f[i]=f[p[i]]+val(p[i],i)f[j]+val(j,i)

证明:
由于 p[i]i 的最优决策,则有 对于 i[1,n],j[0,p[i]],都有 f[p[i]]+val(p[i],i)f[j]+val(j,i)
另外 i[i+1,n],因为 jp[i]i<i,由 val(i,j) 的四边形不等式,得 val(j,i)+val(p[i],i)val(j,i)+val(p[i],i),即 val(p[i],i)val(p[i],i)val(j,i)val(j,i),则与上式相加得 f[p[i]]+val(p[i],i)f[j]+val(j,i),即 对于 i>i 而言,决策 p[i] 要比 jp[i] 更优,得证

故在遍历 i 的同时,可以同时更新后面 i>i 的决策,但这样暴力更新的时间复杂度仍需要 O(n),考虑到对于决策肯定是是一段一段的,不妨将整个区间看成若干个节点,每个节点中存储 j,l,r,表示对于 i[l,r]i 来说,其最优决策为 j,用队列维护这样的节点,i由于 p[i] 具有单调性,即可以二分出这样的临界点,不妨从后往前遍历,如果队列尾节点的左端点的决策 i 要更优,则弹出该节点,直到找到一个节点左端点决策 i 更差,但右端点 i 更优,这时可以该节点的左右端点内二分出临界位置,然后再在队列中插入一个左端点为临界位置,右端点为 n,最优决策为 i 的节点,不难发现,总体上,队列删除和添加节点的时间复杂度都为 O(n),瓶颈在于二分,故可以保证整体上时间复杂度为 O(nlogn),另外,找对于当前 i 的最优决策并不需要二分,因为在遍历 i 的时候,对于 i 前面的数都没用,可以将这部分节点都删除,这时队列头节点即为最优决策

回到本题,本题很容易得到如下 dp

  • 状态表示:f[i] 表示前 i 句的最小不协调度

  • 状态计算:f[i]=min(f[i],f[j]+|sum[i]sum[j]+ij1L|p),其中 sum[i] 为前 i 个句子的总长度,0j<i

现在证明 |sum[i]sum[j]+ij1L|p 满足四边形不等式
即证明 对于任意的 j<i,都有 val(j,i+1)+val(j+1,i)val(j,i)+val(j+1,i+1)
代入,即证明任意的 j<i,都有 |sum[i+1]sum[j]+i+1j1L|p+|sum[i]sum[j+1]+i(j+1)1L|p|sum[i]sum[j]+ij1L|p+|sum[i+1]sum[j+1]+i+1(j+1)1L|p,设 u=(sum[i]+i)(sum[j]+j)(L+1)v=(sum[i]+i)(sum[j+1]+j+1)(L+1),则即证 |v|p|v+a[i+1]+1|p|u|p|u+a[i+1]+1|p,即证明 |x|p|x+C|p 为减函数,一种常见的证明方法,将整个定义域分为 [,C],[C,0],[0,] 三部分,另外再分别求导即可,这里不再赘述

  • 时间复杂度:O(nlogn)

代码

// Problem: 诗人小G // Contest: AcWing // URL: https://www.acwing.com/problem/content/description/306/ // Memory Limit: 64 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } typedef long double LD; const int N=1e5+5; int t,n,l,p,s[N],opt[N]; LD f[N]; char str[N][35]; int hh,tt; struct Q { int j,l,r; }q[N]; LD val(int j,int i) { LD res=1,t=abs(s[i]-s[j]+i-j-1-l); for(int i=1;i<=p;i++)res*=t; return res+f[j]; } void insert(int i) { int pos=n+1; while(hh<=tt&&val(q[tt].j,q[tt].l)>=val(i,q[tt].l))pos=q[tt--].l; if(hh<=tt&&val(q[tt].j,q[tt].r)>=val(i,q[tt].r)) { int l=q[tt].l,r=q[tt].r; while(l<r) { int mid=l+r>>1; if(val(q[tt].j,mid)>=val(i,mid))r=mid; else l=mid+1; } q[tt].r=r-1; pos=r; } if(pos!=n+1)q[++tt]={i,pos,n}; } int main() { for(scanf("%d",&t);t;t--) { scanf("%d%d%d",&n,&l,&p); for(int i=n;i;i--)scanf("%s",str[i]); for(int i=1;i<=n;i++)s[i]=s[i-1]+strlen(str[i]); hh=0,tt=0; q[0]={0,1,n};int cnt=0; for(int i=1;i<=n;i++) { f[i]=val(q[hh].j,i),opt[i]=q[hh].j; if(q[hh].r==i)hh++,cnt++; q[hh].l=i+1; insert(i); } if(f[n]>1e18)puts("Too hard to arrange"); else { printf("%.0Lf\n",f[n]); for(int i=n;i;i=opt[i]) { for(int j=i;j>opt[i];j--) { printf("%s",str[j]); if(j!=opt[i]+1)printf(" "); } puts(""); } } puts("--------------------"); } return 0; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/16970459.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(67)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示