动态规划之最佳加法表达式

xiaoxiao2021-02-28  41

动态规划之最佳加法表达式

题目

有一个由1..9组成的数字串.问如果将m个加号插入到这个数字串中,各种可能形成的表达式中,值最小的那个表达式的值是多少?

解析

假设数字串的长度为n,有m个括号,插入的所有可能性为n!/m!。如果要把所有可能性全部计算出来再找出最小值,这样计算的时间复杂符是巨大的。

换一个角度思考,从后往前来看:若最后一个加号添加完毕,由于无论加号的位置怎样变化,最后一个加号后面的数值是一个确定的值,那么整个表达式的最小值就是最后一个加号后面的数值加上加号前面的表达式的最小值。 我们假设有m个数字和n个加号,最后一个加号放在了第i个数字的后面, 再假设m个数字和n个加号组成表达式的最小值为min(m,n), 且第i个数字到第n个数字组成的数值为num(i, n), 则有min(m,n) = num(i+1,m) + min(i, n-1)。而min(i, n-1)可以继续看成是有m-1个数字和i和加号的表达式的最小值。 。。。这是什么? 上述推导式每一层表达式都在求最小的值,而最后的值需要从后往前一层一层分解为一个个子问题,这不正好是动态规划的特点嘛! 所以我们可以采用动态规划的思想来考虑这个问题。 在上面的推导中其实已经给出了动态规划中最关键的状态转移方程,下面我们只需要考虑:它的最原始状态是怎样的? 当只有一个加号时,我们需要将这一个加号在每个数字间放一下,看看最小的放置位置在哪;若没有加号,毫无疑问,最小值就是这n个数字形成的数值。 所以,初始状态也有了:当加号个数n为0时,min(m,n) = num(0,m)。据此,我们就可以写出最佳加法表达式的动态规划解法。

这里需要强调一点,就是在写状态转移方程式要注意i的取值:第i个数字后是最后一个加号。i的最小值为:当前n-1个加号挤在一起,比如1+2+3+4+i,第i个数字前有n-1个数字,此时i对应的是第n个数字;i的最大值为:最后一个加号在最后一个数字前面,此时i对应的是第m-1个数字。由于数学运算法则的约束,加号的个数不能多于数字的个数减一,所以i的循环范围为for(i = n; i<=(m-1); i++)。

解法一:递归

#include<iostream> using namespace std; #define MAX_LEN 10 #define INF 2100000000 char a[MAX_LEN]; //存放数字字符串的 int L[MAX_LEN][MAX_LEN]; //存放结果的二维数组 //begin 与 end 均为下标 int my_atoi(char *a, int begin, int end) { int result = 0; int level = 1; int i = end; while(i>=begin) { result += ((a[i]-'0')*level); i--; level *= 10; } return result; } int my_min(int a, int b) { return a<b ? a : b; } //len---数字的个数 n---加号的个数 int best_addtion_expression(char *a, int len, int n) { if(n == 0) { L[len-1][n] = my_atoi(a, 0, len-1); } else if(n >= len) L[len-1][n] = INF; else { int t = INF; for(int i = n; i<len; i++) { int cache = best_addtion_expression(a, i, n-1); if (cache != INF) { t = my_min(t, cache + my_atoi(a, i, len-1)); } } L[len-1][n] = t; } return L[len-1][n]; } int main() { int n = 0; int result = 0; int len = 0; cin >> a>>n; len = strlen(a); result = best_addtion_expression(a, len, n); cout << result<< endl; return 0; }

解法二:递推

#include<iostream> using namespace std; #define MAX_LEN 6 #define INF 2100000000 char a[MAX_LEN]; //存放数字字符串的 int L[MAX_LEN][MAX_LEN]; //存放结果的二维数组 //begin 与 end 均为下标 int my_atoi(char *a, int begin, int end) { int result = 0; int level = 1; int i = end; while(i>=begin) { result += ((a[i]-'0')*level); i--; level *= 10; } return result; } int my_min(int a, int b) { return a<b ? a : b; } //BestAdditionExpresion // len---数字的个数 n---符号的个数 int BAE(char *a, int len, int n) { int i = 0; //数字个数 int j = 0; //加号个数 int z = 0; for (i = 1; i<=len; i++) { L[i][0] = my_atoi(a, 0, i-1); for (j = 1; j<=n; j++) { if (j > i-1) { break; } for (z = j; z<i; z++) { //z为最后一个加号前的数字的编号 //i个数字,j个加号组成的值的最小值 //等于 编号z+1到i的数字组成的数值 加上 L[z][j-1] 的最小值 L[i][j] = my_min(L[i][j], L[z][j-1]+my_atoi(a, z, i-1)); } } } return L[len][n]; } int main() { int n = 0; int result = 0; int len = 0; int i = 0; int j = 0; cin >> a>>n; len = strlen(a); for (i = 0; i<=len; i++) { for (j = 0; j<=n; j++) { L[i][j] = INF; } } result = BAE(a, len, n); cout << result<< endl; return 0; }
转载请注明原文地址: https://www.6miu.com/read-2626946.html

最新回复(0)