【计算机算法设计与分析】漂亮打印问题(C++_动态规划)

文章目录

问题描述

给定由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=ijlk+j−i个字符(包括字间空格字符),且不允许将单词打破;多余的空格数为 M − Σ k = i j l k − j + i M-\Sigma_{k=i}^{j}l_k-j+i M−Σk=ijlk−j+i;除文章的最后一行外,希望每行多余的空格数尽可能少。因此,以各行(最后一行除外)的多余空格数的立方和达到最小作为"漂亮"的标准。试用动态规划算法设计一个"漂亮打印"方案,并分析算法的计算复杂性。

算法原理

本题使用动态规划算法求解,需要维护三个数组,分别是:

  • extraij:表示如果第i到第j个单词放到同一行,那么这行有多少个剩余空格;
  • lcij:从第i到第j个单词放到同一行,那么这行空格数的立方值。维护这个数组会出现以下三种情况:
    • extraij < 0(本行由于字符数M限制,不能容纳i到j个单词):lcij = INF;
    • j == n && extraij >= 0(文章最后一行,不计入统计范围):lcij = 0;
    • 除以上两种情况之外:lcij = extraij的立方;
  • ci:文章从第1到j个单词的空格立方之和;

动态规划状态转移方程如下:
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 lcij=\left\{ \begin{aligned} \inf , & &{extraij<0}\\ 0,& &{ j == n \&\& extraij >= 0 }\\ (extrasij)^3, & &{else}\\ \end{aligned} \right. lcij=⎩ ⎨ ⎧inf,0,(extrasij)3,extraij<0j==n&&extraij>=0else
c j = { 0 , j = 0 m i n ( c i − 1 + l c i j , c j ) , j > 0 cj=\left\{ \begin{aligned} 0, & &{j=0}\\ min(ci - 1 + lcij, cj),& &{j>0}\\ \end{aligned} \right. cj={0,min(ci−1+lcij,cj),j=0j>0

综上,我们得到这三个数组,那又该如何得出每行的划分位置呢?

在每次更新cj时记录positionj = i,即对于以单词j为结尾的一行来说,本行最佳起始位置为单词i。由于动态规划更新的每一个数值都是当前及之前的最优解,因此全局最优解就是最后一个数值。position数组中很多值都是无意义的,因为动态规划是从前往后算的,但是只有最后的数值才是全局的最优,输出时由果导因,即从后往前看。

算法实现

cpp 复制代码
#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
*/

参考资料

  1. 动态规划之整齐打印
  2. 动态规划------漂亮打印问题
相关推荐
徐小夕24 分钟前
表格、文档、甘特、大屏、表单一站打通:pxcharts超级表格4.0正式上线!
前端·算法·github
张张的快乐时光呀!39 分钟前
记录使用 MFC 打开文件对话框读取一张图片并做简单的灰度转换算法简单学习过程
c++·mfc
TechLee1 小时前
跨语言加解密总对不上?这个纯 Go 神库让 AES/RSA 与 PHP、Java 100% 互通
java·后端·算法
东华万里2 小时前
第41篇 C++类与对象核心知识梳理:从底层原理到面试实战
开发语言·c++·大学生专区
qq_589666053 小时前
C语言、C++与C#的区别详解
c语言·c++·c#
月华路3 小时前
G1 新生代对象晋升老年代:实现机制与 GC 日志
java·jvm·算法
万法若空3 小时前
排列组合恒等式
c++·算法
Nil2083 小时前
leetcode 105从前序和中序遍历序列构造二叉树
算法·leetcode·职场和发展
一直C3 小时前
【数据结构】哈希表+算法复杂度与经典排序查找(C语言)
java·linux·开发语言·数据结构·算法·ubuntu·散列表
淡海水3 小时前
04-02-哈希-Dictionary-TKey-TValue-上-核心数据结构
数据结构·算法·c#·哈希算法·编译·字典·dictionary