MENU

最佳加法表达式

September 22, 2018 • Read: 3835 • 算法阅读设置

描述展开目录

给定 n 个 1 到 9 的数字,要求在数字之间最多添加 m 个加号 (加号两边必须有数字,并且不能有两个或两个以上加号相邻),使得所得到的加法表达式的值最小,并输出该值。

输入展开目录

每组数据三行。第一行是整数 n,表示有 n 个数 (0<n≤10)
第二行是整数 m,表示最多可以使用 m 个加号
第三行有 n 个数,每个数的范围都是 1 到 9

样例输入展开目录

5
3
1 2 3 4 5

样例输出展开目录

51

样例解释展开目录

12+34+5=51

解题思路展开目录

1. 剪枝展开目录

首先考虑可以考虑剪枝,总共只有 (n-1) 个位置可以放加号,并且不能有两个或以上的加号相邻,因此加号最多可以用 (n-1)/2 个,再引入贪心的策略,加号越多肯定值越小,所以,如果 m≤(n-1)/2,那就用 m 个,如果 m>(n-1)/2,那就只用 (n-1)/2 个

2.dfs展开目录

首先声明,所有的下标均从 0 开始。用 dfs (int idx) 来枚举所有加号摆放的位置,当 idx==m 时,就计算产生的值,然后更新最小值。dfs (idx) 表示的含义是当前用了 idx 个加号,所以 main 函数里调用 dfs (0)。然后是 dfs 函数体里如何枚举的问题,很简单用一层 for 循环,表示枚举到的位置 i,i<n-1

加号放的位置存在 mark [] 数组里,对于样例 12+34+5,对应的 mark [] 数组值为 mark [0]=1,mark [1]=3。num 数组的作用主要是存放数字列,例如 num0 = 12345,num2=34,方便后面直接用

dfs 函数里枚举完加号之后,如何计算也是一个问题,其实也比较简单,定义两个指针 i,j,j 一直往后遍历,当 j 遍历到 mark [index] 时,就将 numi 的值加在 sum 里,然后 i = j+1,index++,但是这样还有最后一个加号到最后一个数之前的值没加,所以最后还要加上 num [mark [index - 1] + 1][n - 1]

3. 分析时间复杂度展开目录

一个加号就要枚举一遍数字列,m 个加号要枚举 m 次数字列,数字列长度是 n,所以时间复杂度是 O (mn^2^),暂时没想到什么地方如何用动态规划优化,重复计算的部分比较难想,可能我蒟蒻吧.....

代码展开目录

C展开目录
  • #include <stdio.h>
  • #include <algorithm>
  • using namespace std;
  • int n,m;//n和m如题所述
  • int ans = 0x7fffffff;//最终答案,要找最小值所以初始化为int最大值
  • int mark[10];//记录放加号的位置,第1个加号的位置在mark[0]
  • int arr[10];//n个数
  • int num[10][10];//num[i][j]表示i到j的数字串
  • void dfs(int idx)//当前用了idx个加号
  • {
  • if(idx == m)//加号用完了,计算产生式的值
  • {
  • int sum = 0;
  • int index = 0;
  • int i = 0;
  • for(int j = 0;j < n;j++)
  • {
  • if(j == mark[index])
  • {
  • sum += num[i][j];
  • index++;
  • i = j + 1;
  • }
  • }
  • sum += num[mark[index - 1] + 1][n - 1];
  • ans = min(ans,sum);
  • return;
  • }
  • for(int i = 0;i < n - 1;i++)
  • {
  • if(idx == 0)//第一个加号
  • {
  • mark[idx] = i;
  • dfs(idx + 1);
  • }
  • else
  • {
  • if(abs(mark[idx - 1] - i) > 1)//必须满足两个加号之间的距离大于1
  • {
  • mark[idx] = i;
  • dfs(idx + 1);
  • }
  • }
  • }
  • }
  • int main()
  • {
  • scanf("%d",&n);
  • scanf("%d",&m);
  • for(int i = 0;i < n;i++)
  • {
  • scanf("%d",&arr[i]);
  • num[i][i] = arr[i];//i到i产生的数字串就是这个数字自己
  • }
  • if(n == 1) //就一个数就直接输出结束
  • {
  • printf("%d",arr[0]);
  • return 0;
  • }
  • //预处理num数组
  • for(int i = 0;i < n;i++)
  • for(int j = i + 1;j < n;j++)
  • num[i][j] = num[i][j - 1] * 10 + arr[j];
  • //剪枝m
  • if(n > 2 && m > (n - 1) / 2)
  • m = (n - 1) / 2;
  • dfs(0);//开始搜索
  • printf("%d",ans);
  • return 0;
  • }
Java展开目录
  • import java.util.Scanner;
  • public class Main {
  • static int n, m, ans = 0x7fffffff;
  • static int[] mark = new int[10];// 放加号的位置
  • static int[] arr = new int[10];
  • static int[][] num = new int[10][10];
  • public static void main(String[] args) {
  • Scanner cin = new Scanner(System.in);
  • n = cin.nextInt();// 数字个数
  • m = cin.nextInt();// 加号个数
  • for (int i = 0; i < n; i++) {
  • arr[i] = cin.nextInt();
  • num[i][i] = arr[i];
  • }
  • if (n == 1)//特殊情况,一个数就不用添加加号了,直接输出
  • System.out.println(arr[0]);
  • else {
  • for (int i = 0; i < n; i++)
  • for (int j = i + 1; j < n; j++)
  • num[i][j] = num[i][j - 1] * 10 + arr[j];
  • for (int i = 0; i < mark.length; i++)
  • mark[i] = -1;
  • if(n > 2 && m > (n - 1) / 2)//剪枝,有可能根本放不了那么多加号
  • m = (n - 1) / 2;
  • dfs(0);
  • System.out.println(ans);
  • }
  • }
  • static void dfs(int idx) {// 已经用了idx个加号
  • if (idx == m) {//加号用完则开始计算产生的值
  • int sum = 0;
  • int index = 0;
  • int i = 0;
  • for (int j = 0; j < n; j++) {
  • if (j == mark[index]) {
  • sum += num[i][j];
  • index++;
  • i = j + 1;
  • }
  • }
  • sum += num[mark[index - 1] + 1][n - 1];
  • ans = Math.min(ans, sum);
  • return;
  • }
  • for (int i = 0; i < n - 1; i++) {// 枚举放加号的位置
  • if (idx == 0) {
  • mark[idx] = i;
  • dfs(idx + 1);
  • } else {
  • if (Math.abs(mark[idx - 1] - i) > 1) {
  • mark[idx] = i;
  • dfs(idx + 1);
  • }
  • }
  • }
  • }
  • }
Last Modified: October 7, 2018
Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment