【算法设计与分析】最大K乘积

算法课堂展示 最大k乘积

简介
青岛某高校,信安专业算法课程第二次课堂展示

问题描述:

设I是一个n位的十进制整数。如果将I划分为k段,则可得到k个整数。这k个整数的乘积称为I的一个k乘积。试设计一个算法,对于给定的I和k,求出I的最大k乘积。

样例展示:

当n=3,k=2时,给定数字串312
此时要将312三个整数划分为2段,需要在数字串的某个合法位置添加一个乘号。

  1. 3*12=26
  2. 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][j1]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.构造最优解(输出划分结果):

思路分析

  1. 考虑开辟一个二维数组d[i][j]用来记录,在前i个数字中插入j个乘号时,最后一个乘号插入的位置。
  2. 遍历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.时间复杂性分析:

  1. 如前文所述,如不采取动态规划算法,而是暴力枚举乘号的位置,开始时,第一个乘号有(n-1)种选择,第二个乘号有(n-2)种选择。。。。。因此时间复杂度为组合数
  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;
}

成果展示

请添加图片描述

  • 3
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值