ALGO-17_蓝桥杯_算法训练_乘积最大(DP)

问题描述 

  今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:

  设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。

  同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:

  有一个数字串:312, 当N=3,K=1时会有以下两种分法:

  3*12=36
  31*2=62

  这时,符合题目要求的结果是:31*2=62

  现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。

输入格式 

  程序的输入共有两行:
  第一行共有2个自然数N,K(6≤N≤401≤K≤6)
  第二行是一个长度为N的数字串。


输出格式 

  输出所求得的最大乘积(一个自然数)。

  样例输入

  4 2
  1231
样例输出
62

 

记:

由于DP题目的解法仍不熟悉,故上网搜寻解题思路

(代码参考:https://blog.csdn.net/fine_rose/article/details/63685548)

 参考代码:

 1 #include <stdio.h> 
 2 #define MIN(X,Y) (X)<(Y)?(X):(Y)
 3 
 4 int n,k;
 5 long long num[45] = {0};
 6 long long dp[45][6] = {0};/*可使用的数字,*号的个数*/
 7 
 8 long long cut(int l,int r)
 9 {
10     int i;
11     long long end = 0;
12     for (i = l ; i <= r ;i ++)
13     {
14         end = end*10 + num[i];        
15     }
16     return end;
17 }
18 
19 void init()
20 {
21     int i;
22     char tmp[45] = {0};
23     scanf("%d %d",&n,&k);
24     scanf("%s",&tmp);
25     for (i = 1 ; i <= n ; i ++)
26     {
27         num[i] = tmp[i-1]-'0';
28     }
29     for (i = 1 ; i <= n ; i ++)
30     {
31         dp[i][0] = cut(1,i);
32     }
33     return ;
34 }
35 
36 void find()
37 {
38     int i,j;
39     int a,b;
40         
41     for (i = 2 ; i <= n ; i ++)/*枚举能使用数字(1-i),(至少需要2数字才可添加*号)*/
42     {
43         j = MIN(i-1,k);/*枚举在能使用数字中最多能放的*个数*/
44         for (a = 1 ; a <= j ; a ++)/*枚举j个乘号情况下的数字分布*/
45         {
46             for (b = a ; b < i ; b ++)/*枚举在能使用数字内的不同组合*/
47             {
48                 if (dp[b][a-1]*cut(b+1,i) > dp[i][a])
49                 {
50                     dp[i][a] = dp[b][a-1]*cut(b+1,i);/*存放最大值*/
51                 }
52             }
53         }
54     }    
55     
56     return ;
57 }
58 
59 int main(void)
60 {
61     init();
62     find();
63     printf("%lld",dp[n][k]);
64     return 0;
65 }

 

 

在过完思路后,可以发现由于会遍历不同长度下的可能结果,而我们仅仅是要长度为n时的最大乘积,显然程序做了大量的无用功

而到这里,思路也有所启发

从题目中,我们可以得到的隐藏条件是n>1,且n>k

而题目的样例输入

4 2

1231

指两个乘号,放在4个数据中间(左右两端不能放),那么

第一个乘号的可能出现位置为:1*2*3*1 

第二个乘号的可能出现位置为:12*3*1 (至少已放了一个乘号)

 显然,后面的乘号出现位置前面的乘号的位置有关系,可以通过标记摆放乘号,并用递归遍历不同的乘号(dfs)

这样,我们就可以得到一种情况下的数字分割

通过查乘号的出现位置,完成数字之间的相乘,并比较最大值,将最大值保存

 

改进代码:

 1 #include <stdio.h> 
 2 #define MAX 46
 3 
 4 int n,k;
 5 long long num[MAX] = {0};/*每一位的数据存储*/
 6 int vis[MAX] = {0};    /*每一位乘号的使用标记*/
 7 long long max = 0;        /*最大值存储*/
 8 
 9 void init()
10 {
11     int i;
12     char tmp[MAX] = {0};
13     scanf("%d %d",&n,&k);
14     scanf("%s",&tmp);
15     for (i = 1 ; i <= n ; i ++)
16     {
17         num[i] = tmp[i-1]-'0';
18     }
19     return ;
20 }
21 
22 long long cut(int l,int r)
23 {
24     int i;
25     long long end = 0;
26     for (i = l ; i <= r ;i ++)
27     {
28         end = end*10 + num[i];        
29     }
30     return end;
31 }
32 
33 void dfs(int x) 
34 {
35     /*隐藏条件:N>1,N>K*/
36     int i;
37     int a,b;/*乘号分割下的数字左右下标*/
38     long long tmp;
39                 
40     if (x > k)/*乘号使用完毕*/
41     {
42         a = 0,b = 1;
43         tmp = 1;
44         /*计算该次搜索中的数字分割后的乘积*/
45         for (i = 1 ; i < n ; i ++)
46         {
47             if (vis[i])
48             {
49                 b = i;
50                 tmp *= cut(a+1,b);                
51                 a = b;
52             }            
53         }
54         tmp *= cut(a+1,n);
55         if (tmp > max)
56         {
57             max = tmp;
58         }
59         return ;
60     }
61      
62     for (i = x ; i < n ; i ++)/*遍历不同位置的乘号*/
63     {
64         if (!vis[i])/*当前位置的乘号未使用*/
65         {
66             vis[i] = 1;
67             dfs(x+1);
68             vis[i] = 0;
69         }
70     }
71         
72     return ;
73 }
74 
75 int main(void)
76 {
77     init();
78     dfs(1);
79     printf("%lld",max);
80     return 0;
81 }

 

转载于:https://www.cnblogs.com/mind000761/p/8672239.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值