最大k乘积
题目:
设I是一个n位十进制整数。如果将I划分为k段,则可得到k个整数。这k个整数的乘积称为I的一个k乘积。试设计一个算法,对于给定的I和k,求出I的最大k乘积。
例如十进制整数 1234 划分为 3 段可有如下情形:
1 × 2 × 34 = 68
1 × 23 × 4 = 92
12 × 3 × 4 = 144
编程任务
对于给定的I 和k,编程计算I 的最大k 乘积。
数据输入
输入的第1 行中有2个正整数n和k。正整数n是序列的长度;正整数k是分割的段数。接下来的一行中是一个n位十进制整数。(n<=10)
结果输出
计算出的最大k乘积。
输入文件示例 输出文件示例
input.txt
3 2
3 1 2
output.txt
62
由题意知此题为求一个状态值。在动态规划矩阵法中即为其中一个点的值。
猜想:求一个i位的数分解为j个 部分后的最大积,等于求k位(1<=k<n)的数分解为j-1个部分后的最大积乘以剩下的那个数m[k+1][i]
可得出动态规划方程:
dp[i][j]=m[1][i] j==1
dp[i][j]= dp[k][j-1]*m[k+1][i],j>1
根据方程,可用矩阵法写出算法如下:
#include<iostream>
#include<stdlib.h>
#include<algorithm>
#include<fstream>
using namespace std;
int n, k;
int a[100];
int dp[100][100];
int m[100][100];
int main() {
ifstream in("input.txt");
if (!in.is_open())
cout << "fail to open the file." << endl;
in >> n >> k;
for (int i = 1; i <= n; i++)
in >> a[i];
//初始化m数组
for (int i = 1; i <= n; i++)
m[i][i] = a[i];
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
m[i][j] = 10 * m[i][j - 1] + a[j];
}
}
//填表
for (int i = 1; i <= n; i++)
dp[i][1] = m[1][i];
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= i; j++) {//这里j如果写1 dp[k][j-1]=0=dp[k][0]就会出现0项0项是不用的 所以出现0项是不允许的
for (int k = 1; k < i; k++) {
dp[i][j] = dp[k][j - 1] * m[k + 1][i];
}
}
}
//输出
ofstream out("output.txt");
out << dp[n][k] << endl;
return 0;
}
游艇租用问题
问题描述
长江游艇俱乐部在长江上设置了n个游艇出租站1,2,…,n。游客可在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站i到游艇出租站j之间的租金为r(i,j),1i<jn。试设计一个算法,计算出从游艇出租站1到游艇出租站n所需的最少租金。
编程任务
对于给定的游艇出租站i到游艇出租站j之间的租金为r(i,j),1i<jn,编程计算从游艇出租站1到游艇出租站n所需的最少租金。
数据输入
由文件input.txt提供输入数据。文件的第1行中有1个正整数n(n<=200),表示有n个游艇出租站。接下来的n-1行是r(i,j),1i<jn。
(例如: 3
5 15
7
表示一共有3个出租站点,其中
第1个站点到第2个的租金为5
第1个站点到第3个的租金为15
第2个站点到第3个的租金为7
同上一题类似,此题也是求某一状态的值。记从地点i到地点j所花费的最小费用为dp[i][j],根据题意可以得出以下动态规划方程:
dp[i][j]=0 i>=j>0
dp[i][j]=dp[i][k]+r[k][j] 0<i<j i<=k<=j
由此动态规划方程,可以得知在填表时要用一次循环,时间复杂度为n^3.以下为算法代码:
#include<iostream>
#include<stdlib.h>
#include<algorithm>
#include<fstream>
using namespace std;
int n;
int r[100][100];
int dp[100][100];
int main() {
ifstream in("input.txt");
if (!in.is_open())
cout << "fail to open the file." << endl;
in >> n;
//初始化r
for (int i = 1; i <= n; i++) {
r[i][i] = 0;
}
for (int i = 1; i <= n-1; i++) {
for (int j = i + 1; j <= n; j++) {
in >> r[i][j];
}
}
//填表
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i >= j)
dp[i][j] = 0;
if (j - i == 1)
dp[i][j] = r[i][j];
}
}
for (int i = 1; i <= n-1; i++) {
for (int j = i + 2; j <= n; j++) {
dp[i][j] = r[i][j];//这里不赋值的话为0 取小的话 后面怎么比都是0
for (int k = i ; k <= j; k++) {
int temp = dp[i][k] + r[k][j];
if (dp[i][j] > temp) {
dp[i][j] = temp;
}
}
}
}
ofstream out("output.txt");
out << dp[1][n] << endl;
in.close();
out.close();
return 0;
}
注意:
1在这种每次填表由k(i<=k<=j)种方式的情况,在k次循环之前要注意是否需要先填一个最大或最小值。因为k次循环后是要根据这k次循环的结果挑选最大或最小值。如果不先赋值,在比较时如果要取这k次循环中的最小值,那么所有代填位全都将会为0.因为初始默认值为0是最小的。所以如果k次循环是要找最小结果,那么在循环之前要赋此位置一个较大值。在本题中赋r(i,j)。即中间不经过任何中转直接到达。
2由于此题只要求从1开始的最小路径,其实可以减少一层循环,将时间复杂度降为n^2
int n;
int r[100][100];
int dp[100][100];
int main() {
ifstream in("input.txt");
if (!in.is_open())
cout << "fail to open the file." << endl;
in >> n;
//初始化r
for (int i = 1; i <= n; i++) {
r[i][i] = 0;
}
for (int i = 1; i <= n-1; i++) {
for (int j = i + 1; j <= n; j++) {
in >> r[i][j];
}
}
//填表
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i >= j)
dp[i][j] = 0;
if (j - i == 1)
dp[i][j] = r[i][j];
}
}
for (int j = 3; j <= n; j++) {
dp[1][j] = r[1][j];//这里不赋值的话为0 取小的话 后面怎么比都是0
for (int k = 1 ; k <= j; k++) {
int temp = dp[1][k] + r[k][j];
if (dp[1][j] > temp) {
dp[1][j] = temp;
}
}
}
ofstream out("output.txt");
out << dp[1][n] << endl;
in.close();
out.close();
return 0;
}
小结:总得来说,遇到一个问题时,首先要判断其是否有最优子结构。即此问题可否通过分解成规模更小的一个同类问题与另一结果的组合的形式。这个规模更小的同类问题就是带求解问题的最优子结构。在确定可以后,接下来便可以尝试列出动态规划递归方程。为什么一定是递归的,因为动态规划一定要用到子最优问题的结果。在列动态规划递归方程时,先考虑规模最小的情况。这种情况一般非常明显,可以直接看出来。在表中通常为第一列,第一行或对角线的数值。为什么要先填极端情况(规模最小情况)因为后续的计算都要依赖前面的结果。列方程的时候,我们的思考角度是自顶向下的,而程序在计算的时候是自底向上的(从小规模到大规模)。