最佳加法表达式 + 高精度

本题来源: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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题需要用到高精度计算,即用数组来存放long long 都装不下的大整数,并用模拟列竖式的办法进行大整数的加法。具体实现可以参考一些高精度模板。 以下是一个可能的解法: ``` #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 55; int m, n; int a[MAXN]; int f[MAXN][MAXN]; int main() { while (cin >> m) { cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } memset(f, 0x3f, sizeof(f)); f[0][0] = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= min(i, m); j++) { for (int k = j-1; k < i; k++) { f[i][j] = min(f[i][j], f[k][j-1] + (a[i]-a[k+1])); } } } cout << f[n][m] << endl; } return 0; } ``` 对于每组数据,我们先读入m和n,然后读入n个数字。接着,我们用f[i][j]表示前i个数字中放j个加号所得到的最小值。初始时,f[0][0] = 0。 接下来,我们考虑状态转移。对于第i个数字,我们可以选择在它前面放一个加号,也可以不放。如果不放,那么f[i][j]就等于f[i-1][j]。如果放,那么我们需要在前面j-1个数字中再放一个加号,使得前面的数字和最小。因此,我们可以枚举前面的加号放在哪里,然后取最小值即可。 最后,输出f[n][m]即可。 注意,这里的加号是指在数字之间放置的加号,而不是在数字后面加上一个数。因此,我们需要在读入数字时,将它们存入一个数组中,然后在状态转移时,计算两个数字之间的差。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值