[NOI2009]诗人小G
题目描述
小G是一个出色的诗人,经常作诗自娱自乐。但是,他一直被一件事情所困扰,那就是诗的排版问题。
一首诗包含了若干个句子,对于一些连续的短句,可以将它们用空格隔开并放在一行中,注意一行中可以放的句子数目是没有限制的。小G给每首诗定义了一个行标准长度(行的长度为一行中符号的总个数),他希望排版后每行的长度都和行标准长度相差不远。显然排版时,不应改变原有的句子顺序,并且小G不允许把一个句子分在两行或者更多的行内。在满足上面两个条件的情况下,小G对于排版中的每行定义了一个不协调度, 为这行的实际长度与行标准长度差值绝对值的P次方,而一个排版的不协调度为所有行不协调度的总和。
小G最近又作了几首诗,现在请你对这首诗进行排版,使得排版后的诗尽量协调(即不协调度尽量小),并把排版的结果告诉他。
输入输出格式
输入格式:
输入文件中的第一行为一个整数T,表示诗的数量。
接下来为T首诗,这里一首诗即为一组测试数据。每组测试数据中的第一行为三个由空格分隔的正整数N,L,P,其中:N表示这首诗句子的数目,L表示这首诗的行标准长度,P的含义见问题描述。
从第二行开始,每行为一个句子,句子由英文字母、数字、标点符号等符号组成(ASCII码33~127,但不包含'-')。
输出格式:
于每组测试数据,若最小的不协调度不超过10^18,则第一行为一个数,表示不协调度。接下来若干行,表示你排版之后的诗。注意:在同一行的相邻两个句子之间需要用一个空格分开。
如果有多个可行解,它们的不协调度都是最小值,则输出任意一个解均可。若最小的不协调度超过10^18,则输出“Too hard to arrange”(不含引号)。每组测试数据结束后输出“--------------------”(不含引号),共20个“-”,“-”的ASCII码为45,请勿输出多余的空行或者空格。
输入输出样例
输入样例#1:
4
4 9 3
brysj,
hhrhl.
yqqlm,
gsycl.
4 9 2
brysj,
hhrhl.
yqqlm,
gsycl.
1 1005 6
poet
1 1004 6
poet
输出样例#1:
108
brysj,
hhrhl.
yqqlm,
gsycl.
32
brysj, hhrhl.
yqqlm, gsycl.
Too hard to arrange
1000000000000000000
poet
题解
决策单调性优化\(DP\)
首先什么是决策单调性?
就是对于每个已经求出的位置都可以对后面的位置进行直接的决策(可能不是最优决策)
并且每个点的最优决策点随着坐标增加单调不降
一般来讲含有决策单调性的题目都会是划分的长度越长花费越大
对于这道题,可以看出决策点是单调不降的
所以可以维护一个单调队列,维护每个决策点的可决策区间
显然一个决策点对越靠后的点做出的决策越劣
所以对于要求的点,先把单调队列中的决策区间的右端点已经在当前点的左边的决策给弹出
然后当前点的\(f_i\)就是队头
然后再从队尾弹元素
一一比较队尾元素与当前元素,如果队尾元素的决策区间的左端点的答案不如当前点对该左端点优,那么这个点就煤油任何意义了,就可以把ta弹出
然后对于最后的队尾元素
需要修改ta的决策区间
直接二分队尾元素的决策区间来求求出从哪个点\(k\)开始当前点的决策就比队尾元素优了
然后直接修改队尾元素决策区间
最后再把当前点加入队列,决策区间就是\(k~n\)
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
# define double long double
const int M = 100005 ;
const double INF = 1e18 ;
const double EPS = 1e-2 ;
using namespace std ;
int n , m , k , cnt ;
int head , tail , pre[M] , po[M] ;
string s[M] ;
double sum[M] , f[M] ;
struct Node { int l , r , pos ; } q[M] ;
inline double Fpw(double Base , int k) {
double temp = 1 ;
while(k) {
if(k & 1) temp = temp * Base ;
Base = Base * Base ; k >>= 1 ;
}
return temp ;
}
inline double Calc(int j , int i) {
if(j > i) swap(i , j) ;
return f[j] + Fpw(fabs(sum[i] - sum[j] + (i - j - 1) - m) , k) ;
}
int main() {
int T ; scanf("%d",&T) ;
while(T --) {
scanf("%d%d%d",&n,&m,&k) ;
for(int i = 1 , len ; i <= n ; i ++) {
cin >> s[i] ;
len = s[i].length() ;
sum[i] = sum[i - 1] + len ;
}
head = 1 ; tail = 1 ;
q[head] = (Node) { 1 , n , 0 } ;
for(int i = 1 , j ; i <= n ; i ++) {
while(head < tail && q[head].r < i)
++ head ;
j = q[head].pos ; q[head].l ++ ;
f[i] = Calc(j , i) ;
pre[i] = j ;
while(head < tail && Calc(q[tail].pos , q[tail].l) >= Calc(i , q[tail].l))
-- tail ;
int l = q[tail].l , r = q[tail].r , posi = q[tail].pos , ret = r + 1 ;
while(l <= r) {
int mid = (l + r) >> 1 ;
if(Calc(i , mid) <= Calc(posi , mid)) ret = mid , r = mid - 1 ;
else l = mid + 1 ;
}
if(ret <= n) {
q[tail].r = ret - 1 ;
q[++tail] = (Node) { ret , n , i } ;
}
}
if(f[n] - EPS > INF) printf("Too hard to arrange\n") ;
else {
printf("%lld\n",(long long)f[n]) ;
memset(po , 0 , sizeof(po)) ;
int now = n , cnt = 0 ;
while(now) {
po[++cnt] = now ;
now = pre[now] ;
}
for(int i = cnt ; i >= 1 ; i --) {
for(int j = po[i + 1] + 1 ; j <= po[i] ; j ++) {
cout << s[j] ;
if(j != po[i]) printf(" ") ;
}
printf("\n") ;
}
}
printf("--------------------\n") ;
}
return 0 ;
}