本题来源:MOOC郭炜老师算法学习练习
最佳加法表达式
问题重述
给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值。例如,在1234中摆放1个加号,最好的摆法就是12+34,和为36
输入
有不超过15组数据
每组数据两行。第一行是整数m,表示有m个加号要放( 0<=m<=50)
第二行是若干个数字。数字总数n不超过50,且 m <= n-1
输出
对每组数据,输出最小加法表达式的值
样例输入
2
123456
1
123456
4
12345
样例输出
102
579
15
提示
要用到高精度计算,即用数组来存放long long 都装不下的大整数,并用模拟列竖式的办法进行大整数的加法。
思路分析:
还是比较规范的动态规划算法题,我们设定当前状态是 ans[n][m] 表示前 n 个数里放入 m 个加号后所能得到的最小值。状态转移就是 ans[n][m]=min(ans[n][m],ans[i][m-1]+temp) ,其中 ans[n][m] 表示我们要求的当前状态, ans[i][m-1] 表示要从哪些状态转移过来,i 的取值有多种我们要保证前 i 个数能放下 m-1 个加号且 i 之后还要有数即 temp 我们要计算每一种可能所以加号能放的位置要全部遍历。但因为数据太大我们要用到高精度算法,这里的运算我们都要重新定义我们需要的运算有比较大小和相加运算,一开始我用数组模拟大数的相加和比较后来发现过于麻烦(也是因为我水平不够),后来想到字符串的方法较多便又用了字符串模拟,发现较简洁。
C++代码:
#include<iostream>
#include<cstring>
using namespace std;
string s;//表示每次要处理的大数
string ans[52][52];//定义一个答案数组存储前n个数里放入m个加号后所能得到的最小值
void init()//初始化答案数组
{
for(int i=0;i<52;i++)
for(int j=0;j<52;j++)
ans[i][j]="";
}
string mins(string a,string b)//高精度字符串模拟比较大小
{
if(a.size()==0)//a的初始值可能为空这时返回b
return b;
if(a.size()!=b.size())//否则若长度不等返回短的那个
return a.size()>b.size()?b:a;
else
return a>b?b:a;//长度相等就比较字符串字典序返回字典序小的
}
string add(string a,string b)//高精度字符串模拟大数相加
{
string c=a.size()>b.size()?a:b;//定义一个c或得a、b中较长的那个
c="0"+c;//在c前接一个0防止最高位有进位
int i=a.size()-1,j=b.size()-1,k=c.size()-1;//从最低位开始加
while(i>=0||j>=0)//在a、b两数没加完时一直循环
{
c[k--]=a[i--]+b[j--]-'0';//获得两位相加后的数因为都是字符所以要减去一个'0'
if(j<0)//当其中一个加完后将另一个赋完
{
while(i>=0)
{
c[k--]=a[i--];
}
}
if(i<0)
{
while(j>=0)
{
c[k--]=b[j--];
}
}
}
for(i=c.size()-1;i>0;i--)//从最末尾开始检查有没有超过9的数字
{
if(c[i]>'9')//如果是大于9的数字就向高位进一
{
c[i]-=10;
c[i-1]+=1;
}
}
return c[0]=='0'?c.substr(1,c.size()-1):c;//最后检查最高位有无进位有就直接返回c没有则不返回c的最高位
}
void dfs(int n,int m)//此函数是求前n个数插入m个加号后的最小值是多少
{
if(m==0&&n>0)//如果加号数为0则直接是剩下的数字
{
ans[n][m]=s.substr(0,n);
}
else
{
for(int i=m;i<n;i++)//否则找出能放当前一个加号的位置,即确保前面的数能放下m-1个加号且后面也有数可加
{//即要保证前面至少有m个数且后面至少有一个数
string temp=s.substr(i,n-i);//获取后面的数
if(ans[i][m-1]=="")//如果前面的数未求出则递归调用函数求值
{
dfs(i,m-1);
}
ans[n][m]=mins(ans[n][m],add(ans[i][m-1],temp));//比较加号放在各个位置所能得到值哪个最小
}
}
}
int main()
{
int m;
while(cin>>m>>s)//多组输入加号数和字符串表示的大数
{
init();//多组输入所以答案数组要初始化
dfs(s.size(),m);//求出所有数中放m个加号所能得最小值
cout<<ans[s.size()][m]<<endl;
}
return 0;
}