【洛谷P1912】诗人小G

题目

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

思路

\(\text{len}_i\) 表示字符串的长度前缀和。可以直接把后面的空格长度算进去。
\(f_i\) 表示前 \(i\) 句诗的最小不协调度,显然有

\[f_i=\min_{j<i}(f_j+|\text{len}_i-\text{len}_j-1-m|^P) \]

手画分类讨论一下不难发现,对于 \(j<i\)\(j\) 的决策点一定小于等于 \(i\) 的决策点。也就是满足四边形不等式。
那么直接用单调队列维护一下就好了。要转移到 \(i\) 的时候,就不断检查队首,如果队首前两位的函数交点在 \(i\) 之前就弹出。
插入 \(i\) 的时候不断检查队尾,如果队尾的两个元素的交点在队尾和 \(i\) 的交点后就弹出。
判断答案超过 \(10^{18}\) 可以直接用 long double,这样如果超过了 \(10^{18}\) 虽然会损失精度导致结果错误,但是正确结果一定是超过 \(10^{18}\)。直接判断无解即可。
时间复杂度 \(O(Tn\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;

const int N=100010;
const ld Inf=1e18;
int Q,n,m,p,hd,tl,g[N],q[N],len[N],pre[N];
ld f[N];
char s[N][32];

ld fpow(ld x,ll k)
{
	ld ans=1;
	for (;k;k>>=1,x=x*x)
		if (k&1) ans=ans*x;
	return ans;
}

ld calc(int i,int j)
{
	return f[j]+fpow(abs(len[i]-len[j]-1-m),p);
}

int binary(int i,int j)
{
	int l=j,r=n,mid;
	while (l<=r)
	{
		mid=(l+r)>>1;
		if (calc(mid,i)<calc(mid,j)) l=mid+1;
			else r=mid-1;
	}
	return l-1;
}

void dfs(int n)
{
	if (!n) return;
	dfs(pre[n]);
	for (int i=pre[n]+1;i<n;i++)
		printf("%s ",s[i]);
	printf("%s\n",s[n]);
}

int main()
{
	scanf("%d",&Q);
	while (Q--)
	{
		memset(f,0,sizeof(f));
		hd=tl=1; q[1]=0;
		scanf("%d%d%d",&n,&m,&p);
		for (int i=1;i<=n;i++)
		{
			scanf("%s",s[i]);
			len[i]=len[i-1]+strlen(s[i])+1;
		}
		for (int i=1;i<=n;i++)
		{
			while (hd<tl && g[q[hd]]<i) hd++;
			f[i]=calc(i,q[hd]); pre[i]=q[hd];
			while (hd<tl && g[q[tl-1]]>=binary(q[tl],i)) tl--;
			g[q[tl]]=binary(q[tl],i); q[++tl]=i;
		}
		if (f[n]>Inf) printf("Too hard to arrange\n");
			else printf("%lld\n",(ll)f[n]),dfs(n);
		printf("--------------------\n");
	}
	return 0;
}
posted @ 2021-02-23 11:14  stoorz  阅读(74)  评论(0编辑  收藏  举报