P1912 [NOI2009] 诗人小G(决策单调性优化DP)

洛谷题目传送门

题目描述

小 G 是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。

一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小 G 给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小 G 不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小 G 对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的 P 次方,而一个排版的不协调度为所有行不协调度的总和。

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

几句废话

一个月前的坑,终于来补了
之前死活没听懂,现在感觉很简单

解题思路

首先把DP式列出来
F [ i ] F[i] F[i] 表示以第i个句子作为某一个结尾的最小不协调度, s u m [ i ] sum[i] sum[i]表示1~i的字符串的长度总和
F [ i ] = m i n ( F [ j ] + ∣ s u m [ i ] − s u m [ j ] + i − j − 1 − L ∣ P ) F[i]=min(F[j]+\vert sum[i]-sum[j]+i-j-1-L\vert^P) F[i]=min(F[j]+sum[i]sum[j]+ij1LP)
也很好理解吧,就是枚举上一个位置,当前行的长度就是字符串的长度和+空格个数
这个式子有高次项,不好优化,于是
通过简单的打表我们发现它满足一个很重要的性质

决策单调性

其含义为,若对于 J ,他的最优决策点为 i ,则 J 后面的位置的最优决策点一定大于等于 i
或者可以理解为
1        2        3      4      5 1\;\;\;2\;\;\;3\;\;4\;\;5 12345
p 1    p 2    p 3    p 4    p 5 p_1\;p_2\;p_3\;p_4\;p_5 p1p2p3p4p5

上边表示每个位置,下边是每个位置的最优决策(也就是从那个点转移更优)
决策单调性的含义就是 p 1 ≤ p 2 ≤ p 3 ≤ p 4 ≤ p 5 p_1 \leq p_2 \leq p_3 \leq p_4 \leq p_5 p1p2p3p4p5
有了这个性质我们就来优化吧
可以发现在任意时刻,全局的最有决策一定是块状分布的
所以我们用一个队列维护这些块
最初只有一个元素,范围为1~n,这些点都从0转移更优
然后每加一个元素,就取队头的决策点就可以了,然后把不合法的块删掉
因为决策单调性,所以当前位置如果成为最优决策点,一定是某一个后缀
那就从后开始检查,如果这个块左端点从i转移更优,就把这个块删掉
如果找的了某个块,不优了那么就在这个块内二分,找到第一个从i转移更优的位置,然后把这块从那个位置分裂,并把后面加一个到n的快,这里由i转移更优

坑点

本题如果中间就大于1e18 那么就炸了
所以要用long double 就好了

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double LD;
const LL N = 1e5+7;
struct node
{
	LL l,r,j;
};
char s[N][50];
LL sum[N],len[N],n,L,p;
LD F[N];
LL Pos[N];
deque<node> q;
node Node(LL j,LL l,LL r)
{
	node tmp;
	tmp.j=j;
	tmp.l=l;
	tmp.r=r;
	return tmp;
}
LD Pow(LD a,LL b)
{
	LD res=1;
	while(b)
	{
		if(b&1) res=res*a;
		a=a*a;
		b/=2;	
	}	
	return res;
} 
LD val(LL i,LL j)
{
	return F[j]+Pow(llabs(sum[i]-sum[j]+i-j-1-L),p);
}
LL cnt=0;
LL Find(LL i,LL j,LL L,LL R)
{
	cnt++;
	LL l=L,r=R,mid,ans=L-1;
	while(l<=r)
	{
		mid=(l+r)>>1;
		if(val(mid,j)<val(mid,i))
		{
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return ans;
}	
LL seq[N],top=0;
void solve()
{
	scanf("%lld%lld%lld",&n,&L,&p);
	memset(F,0,sizeof(F));
	memset(Pos,0,sizeof(Pos));
	for(LL i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
		len[i]=strlen(s[i]+1);
		sum[i]=sum[i-1]+len[i];
	}
	while(!q.empty()) q.pop_back();
	q.push_back(Node(0,1,n));
	for(LL i=1;i<=n;i++)
	{
		while(!q.empty()&&q.front().r<=i-1) q.pop_front();		
		q.front().l=i+1;
		LL j=q.front().j;
		F[i]=val(i,j);
		Pos[i]=j;
		while(!q.empty()&&val(q.back().l,q.back().j)>=val(q.back().l,i))  q.pop_back();
		LL pos=0;
		if(!q.empty()) 
		{
			pos=Find(i,q.back().j,q.back().l,q.back().r);
			q.back().r=pos;
		}
		if(pos+1<=n) q.push_back(Node(i,pos+1,n));
	}
	top=0;
	for(LL i=n;i;i=Pos[i])
	seq[++top]=i;
	seq[top+1]=0;
	if(F[n]>1e18) printf("Too hard to arrange\n");
	else
	{
		printf("%lld\n",(LL)(F[n]+0.5));
		for(LL i=top;i>=1;i--)
		{
			for(LL j=seq[i+1]+1;j<seq[i];j++)
			{
				printf("%s ",s[j]+1);
			}
			printf("%s\n",s[seq[i]]+1);
		}		
	}
	printf("--------------------\n");
}
int main()
{
	LL T;
	scanf("%lld",&T);
	while(T--)
	{
		solve();
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值