算法课堂展示 最大k乘积
简介
青岛某高校,信安专业算法课程第二次课堂展示
问题描述:
设I是一个n位的十进制整数。如果将I划分为k段,则可得到k个整数。这k个整数的乘积称为I的一个k乘积。试设计一个算法,对于给定的I和k,求出I的最大k乘积。
样例展示:
当n=3,k=2时,给定数字串312
此时要将312三个整数划分为2段,需要在数字串的某个合法位置添加一个乘号。
- 3*12=26
- 31*2=62
此时,该问题的最优解为62,对应第二种划分方式。
思路分析:
将一个n位整数划分为k段,相当于在n段数字中合法的位置中加入k-1个乘号。
如果暴力的枚举乘号出现的位置,该问题的时间复杂度应该是O((n-1)!),时间复杂度太高,因此考虑动态规划的思路解决该问题。
1.问题的一般化表示:
f[i][j] 表示在前i个数字当中插入j个合法乘号的最大值。
m[i][j] 表示在数字串从i到j位置的数字组成数字的值的大小。
举例:当I=312时,m[1][1]=3,m[1][2]=31,m[2][3]=12…
2.最优子结构:
用 f(i,j) 表示在前i个数字中插入j个乘号得到的最优解,则f(i,j)=f(t,j-1)*m[t+1][i]
此时的f(t,j-1)必为子问题的最优解。
反证法:
假设f(t,j-1)不是子问题的最优解,则一定存在一个e(t,j-1)>f(t,j-1)。
此时,在不等式的左右两边同时乘上m[t+1][i]得到:
e(t,j-1)*m[t+1][i]>f(t,j-1)*m[t+1][i]=f(i,j)
与前提相矛盾,于是得证f(t,j-1)是原问题的最优解。
3.问题的递归定义:
f [ i ] [ j ] = { n u m [ 1 ] [ i ] j=0 m a x ( f [ t ] [ j − 1 ] ∗ n u m [ t + 1 ] [ i ] ) j>0,1<=t<i f[i][j]= \begin{cases} num[1][i]& \text{j=0}\\ max(f[t][j-1]*num[t+1][i])& \text{j>0,1<=t<i} \end{cases} f[i][j]={num[1][i]max(f[t][j−1]∗num[t+1][i])j=0j>0,1<=t<i
4.自底向上的求解最优值:
将n个数字划分为k段(即在n个数字中插入k-1个乘号),依次遍历将前t位划分为k-1段的最优解并乘上其余位数组成的十进制数的数值的最大值存放在f[t][k-1]当中,最终输出f[n][k]即为最优解。
关键代码:
for(int i=1;i<=n;i++){ //枚举处理多少个数字
for(int j=1;j<=k;j++){ //枚举插入多少个乘号
for(int t=1;t<i;t++){ //枚举最后乘号的乘号插入哪里(将前多少个数字划为一类)
f[i][j]=max(f[i][j],f[t][j-1]*num[t+1][i]);
}
}
}
5.构造最优解(输出划分结果):
思路分析:
- 考虑开辟一个二维数组d[i][j]用来记录,在前i个数字中插入j个乘号时,最后一个乘号插入的位置。
- 遍历d数组,用变量o来记录上一次划分的末尾,每一次输出num[o][d[i][j]]即为划分结果(说的不是很清晰,但是看代码应该可以明白)
int z=n;
for(int i=n;i>=1;i--){
for(int j=k;j>=1;j--){
o=s.substr(d[i][j],n-d[i][j]);
int x=n-d[i][j];
if(i==z){
cout<<o;
}else{
if(o!="")
cout<<"*"<<o;
}
i-=x;
n-=x;
}
}
o=s.substr(0,n);
if(o!="")
cout<<"*"<<o;
printf("=%d\n",f[c][e]);
6.时间复杂性分析:
- 如前文所述,如不采取动态规划算法,而是暴力枚举乘号的位置,开始时,第一个乘号有(n-1)种选择,第二个乘号有(n-2)种选择。。。。。因此时间复杂度为组合数
- 采用动态规划算法时间复杂度为O(n3)
代码展示
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N][N],num[N][N],a[N];
int d[N][N];
int ans[N];
int cnt;
int n,k;
int main(){
cin>>n>>k;
k--;
string s;
int c,e;
c=n,e=k;
cin>>s;
for(int i=1;i<=s.size();i++) a[i]=s[i-1]-'0';
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
num[i][j]=num[i][j-1]*10+a[j];
}
}
for(int i=1;i<=n;i++) f[i][0]=num[1][i]; //处理边界
for(int i=1;i<=n;i++){ //枚举处理多少个数字
for(int j=1;j<=k;j++){ //枚举插入多少个乘号
for(int t=1;t<i;t++){ //枚举最后乘号的乘号插入哪里(将前多少个数字划为一类)
if(f[t][j-1]*num[t+1][i]>f[i][j]) d[i][j]=t;
f[i][j]=max(f[i][j],f[t][j-1]*num[t+1][i]);
}
}
}
printf("最大值:%d\n",f[n][k]);
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
printf("%d ",d[i][j]);
}
printf("\n");
}
string o,p,q;
printf("表达式:");
int z=n;
for(int i=n;i>=1;i--){
for(int j=k;j>=1;j--){
o=s.substr(d[i][j],n-d[i][j]);
int x=n-d[i][j];
if(i==z){
cout<<o;
}else{
if(o!="")
cout<<"*"<<o;
}
i-=x;
n-=x;
}
}
o=s.substr(0,n);
if(o!="")
cout<<"*"<<o;
printf("=%d\n",f[c][e]);
for(int i=1;i<=c;i++){
for(int j=0;j<=e;j++){
printf("%d ",f[i][j]);
}
printf("\n");
}
return 0;
}