???????给定由n个英文单词组成的一段文章,每个单词的长度 (字符个数)依序为 l 1 , l 2 , . . . , l n l_1, l_2, ..., l_n l1?,l2?,...,ln?。要在一台打印机上将这段文章“漂亮”地打印出来。打印机每行最多可打印 M个字符。这里所说的“漂亮”的定义如下:在打印机所打印的每一行中,行首和行尾可不留空格;行中每两个单词之间留一个空格;如果在一行中打印从单词i到单词j的字符,则按打印规则,应在一行中恰好打印 Σ k = i j l k + j ? i \Sigma_{k=i}^{j}l_k+j-i Σk=ij?lk?+j?i个字符(包括字间空格字符),且不允许将单词打破;多余的空格数为 M ? Σ k = i j l k ? j + i M-\Sigma_{k=i}^{j}l_k-j+i M?Σk=ij?lk??j+i;除文章的最后一行外,希望每行多余的空格数尽可能少。因此,以各行(最后一行除外)的多余空格数的立方和达到最小作为“漂亮”的标准。试用动态规划算法设计一个“漂亮打印”方案,并分析算法的计算复杂性。
本题使用动态规划算法求解,需要维护三个数组,分别是:
动态规划状态转移方程如下:
l
c
[
i
]
[
j
]
=
{
inf
?
,
e
x
t
r
a
[
i
]
[
j
]
<
0
0
,
j
=
=
n
&
&
e
x
t
r
a
[
i
]
[
j
]
>
=
0
(
e
x
t
r
a
s
[
i
]
[
j
]
)
3
,
e
l
s
e
lc[i][j]=\left\{ \begin{aligned} \inf , & &{extra[i][j]<0}\\ 0,& &{ j == n \&\& extra[i][j] >= 0 }\\ (extras[i][j])^3, & &{else}\\ \end{aligned} \right.
lc[i][j]=?
?
??inf,0,(extras[i][j])3,??extra[i][j]<0j==n&&extra[i][j]>=0else?
c
[
j
]
=
{
0
,
j
=
0
m
i
n
(
c
[
i
?
1
]
+
l
c
[
i
]
[
j
]
,
c
[
j
]
)
,
j
>
0
c[j]=\left\{ \begin{aligned} 0, & &{j=0}\\ min(c[i - 1] + lc[i][j], c[j]),& &{j>0}\\ \end{aligned} \right.
c[j]={0,min(c[i?1]+lc[i][j],c[j]),??j=0j>0?
综上,我们得到这三个数组,那又该如何得出每行的划分位置呢?
???????在每次更新c[j]时记录position[j] = i,即对于以单词j为结尾的一行来说,本行最佳起始位置为单词i。由于动态规划更新的每一个数值都是当前及之前的最优解,因此全局最优解就是最后一个数值。position数组中很多值都是无意义的,因为动态规划是从前往后算的,但是只有最后的数值才是全局的最优,输出时由果导因,即从后往前看。
#include <bits/stdc++.h>
#define INF 1000
using namespace std;
int wordsLen(int wordsL[], int i, int j) {
//计算从i到j的单词总长度
int sum = 0;
for (int k = i; k <= j; k++) {
sum += wordsL[k];
}
return sum;
}
int main() {
int n, M, extra[100][100], lc[100][100], c[100], l[100], position[100];
string words[100];
c[0] = 0;
cin >> n >> M;//n个单词,每行最多M个字符
for (int i = 1; i <= n; i++) {
cin >> words[i];
l[i] = words[i].size();
c[i] = INF;
}
for (int i = 1; i <= n; i++) { //此行从第i个开始放
for (int j = i; j <= n; j++) { //放到第j个
extra[i][j] = M - j + i - wordsLen(l, i, j); //如果i到j放到一行,那么这行有多少个剩余空格
if (extra[i][j] < 0) //这行放不下这么多单词
lc[i][j] = INF;
else if (j == n && extra[i][j] >= 0) //最有一行
lc[i][j] = 0;
else //正常情况
lc[i][j] = pow(extra[i][j], 3);
if (c[i - 1] + lc[i][j] < c[j]) { //从i处分行是否空格更少
c[j] = c[i - 1] + lc[i][j]; //整篇文章从1到j存放单词的空格立方之和
//position数组很多值都是无意义的,因为动态规划是从前往后算的,但是只有最后的数值才是全局的最优,输出时由果导因,即从后往前看。
position[j] = i; //对于以j为结尾的一行来说,本行最佳起始位置为i
}
}
}
stack<int> st;
int i = n;
while(i>0) {
st.push(position[i]);
i = position[i]-1;
}
st.pop();
for (int i = 1; i <= n; i++) {
if (!st.empty()&&i == st.top())
{
cout << endl;
st.pop();
}
cout << words[i] << ' ';
}
cout << "\n总剩余空格数:" << c[n];
return 0;
}
/*
10 8
abc de h polq cs opaqe gh t asd th
*/