304. 诗人小G

题目链接

304. 诗人小G

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

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

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

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

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

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

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

输入格式

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

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

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

输出格式

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

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

若最小的不协调度超过 \(10^{18}\),则输出 Too hard to arrange

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

数据范围

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

测试点 \(T\) \(N\) \(L\) \(P\)
\(1\) \(\le 10\) \(\le18\) \(\le 100\) \(\le5\)
\(2\) \(\le 10\) \(\le 2\times 10^3\) \(\le 6\times 10^4\) \(\le10\)
\(3\) \(\le 10\) \(\le 2\times 10^3\) \(\le 6\times 10^4\) \(\le10\)
\(4\) \(\le 5\) \(\le 10^5\) \(\le 200\) \(\le10\)
\(5\) \(\le 5\) \(\le 10^5\) \(\le 200\) \(\le10\)
\(6\) \(\le 5\) \(\le 10^5\) \(\le 3\times 10^6\) \(2\)
\(7\) \(\le 5\) \(\le 10^5\) \(\le 3\times 10^6\) \(2\)
\(8\) \(\le 5\) \(\le 10^5\) \(\le 3\times 10^6\) \(\le10\)
\(9\) \(\le 5\) \(\le 10^5\) \(\le 3\times 10^6\) \(\le10\)
\(10\) \(\le 5\) \(\le 10^5\) \(\le 3\times 10^6\) \(\le10\)

所有测试点中均满足句子长度不超过 \(30\)\(P \ge 1\)

输入样例:

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)\),对于定义域上的任何四个整个整数 \(a\leq b\leq c\leq d\),都有 \(w(a,d)+w(b,c)\geq w(a,c)+w(b,d)\),则称函数 \(w\) 满足四边形不等式
四边形不等式的另外一个等价定义:对于定义域上的任何两个整数 \(a<b\),都有 \(w(a,b+1)+w(a+1,b)\geq w(a,b)+w(a+1,b+1)\)
证明:
对于 \(a<c\),则 \(a< a+1\leq c<c+1\),有 \(w(a,c+1)+w(a+1,c)\geq w(a,c)+w(a+1,c+1)\)
对于 \(a+1<c\),则 \(a+1< a+2\leq c<c+1\),有 \(w(a+1,c+1)+w(a+2,c)\geq w(a+1,c)+w(a+2,c+1)\)
两式相加,得 \(w(a,c+1)+w(a+2,c)\geq w(a,c)+w(a+2,c+1)\)
\(\vdots\)
得对于 \(a< b< c<c+1\)\(w(a,c+1)+w(b,c)\geq 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)\geq w(a,c+1)+w(b,c+2)\)
两式相加,得 \(w(b,c)+w(a,c+2)\geq w(a,c)+w(b,c+2)\)
\(\vdots\)
得对于 \(a<b< c< d\)\(w(b,c)+w(a,d)\geq w(a,c)+w(b,d)\)
显然,\(a=b=c=d\) 时该式依然成立,故有 \(对于a\leq b\leq c\leq d,有w(a,d)+w(b,c)\geq w(a,c)+w(b,d)\),得证
一般证明四边形不等式都是拿该等价定义来证明
一维线性dp的不等式优化
对于形如 \(f[i]=\min_\limits{0\leq j<i}\{f[j]+val(i,j)\}\),如果 \(val(i,j)\) 这个函数满足四边形不等式,则一般可以将一维优化为 \(O(logn)\)

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

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

故在遍历 \(i\) 的同时,可以同时更新后面 \(i'>i\) 的决策,但这样暴力更新的时间复杂度仍需要 \(O(n)\),考虑到对于决策肯定是是一段一段的,不妨将整个区间看成若干个节点,每个节点中存储 \(j,l,r\),表示对于 \(i\in [l,r]\)\(i\) 来说,其最优决策为 \(j\),用队列维护这样的节点,\(\color{red}{遍历{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]+i-j-1-L|^p)\),其中 \(sum[i]\) 为前 \(i\) 个句子的总长度,\(0\leq j<i\)

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

  • 时间复杂度:\(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;
}
posted @ 2022-12-09 22:52  zyy2001  阅读(57)  评论(0编辑  收藏  举报