最佳加法表达式
忽然发现部分题目要求高精度
题目描述
有一个由1…9组成的数字串.问如果将m个加号插入到这个数字串中,在各种可能形成的表达式中,值最小的那个表达式的值是多少。
输入:
5 3
1 2 3 4 5
输出:
24
我们一开始可以采用递归的思路来进行思考。
我们先考虑,最右边的加号放在哪里?
假设数字字符2长度为n,最右边的加号放在第i个数字的后边.
那么现在最小值就是在前i个数字中插入m-1个加号后所能形成的最小值.
那么现在就是减而治之的递归思路就完成了(分解为规模更小的子问题来解决)
这也恰恰满足我们的最优子结构和无后效性
V(m,n)表示前n个数组当中插入m个加号所形成表达式最小值
下面借用以下人家的分析:
设V(m,n)表示在n个数字中插入m个加号所能形成
的表达式最小值,那么:
if m = 0
V(m,n) = n个数字构成的整数
else if n < m + 1
V(m,n) = ∞(不是所求就是最大的)
else
V(m,n) = Min{ V(m-1,i) + num(i+1,n) } ( i = m … n-1)
num(i,j)表示从第i个数字到第j个数字所组成的数。数字编号从1开始算。此操作复杂度是O(j-i+1),为了节省时间我们预处理num(i,j)(就是什么都不干之前把这些值全部算出来)
总时间复杂度:O(mn^2) .
有的题目需要用到高精度模板来进行计算了(long long无法承受得住的时候)高精度模板可以参考我教主的序列的文章
这里我想说的是,你一旦用高精度模板,那么将会变成O(mn^3),应为多了n个元素的数组,你每次操作数组都是O(n)的
下面两个为非高精度的代码
递归型代码
#include <iostream>//最佳加法表达式
#include <cstring>
#include <algorithm>
#include <cstdio>
const int INF=9999999;
int a[1010],num[1010][1010];
using namespace std;
int V(int m,int n)
{//状态转移方程
if(m==0)
return num[1][n];
else if(n<m+1)//加号多了
return INF;
else
{
int t = INF;
for(int i = m;i <= n-1;i++)
t = min(t, V(m-1,i)+num[i+1][n]);
return t;
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))//就是当没有输入的时候退出循环
{
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i)//预处理,节省时间
{
num[i][i]=a[i];//只有一个数组
for(int j=i+1;j<=n;++j)
{
num[i][j]=num[i][j-1]*10+a[j];
}
}
cout<<V(m,n)<<endl;
}
return 0;
}
递推型的可以自己想一下哦
递推型
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;//无穷大
const int N=1010;
int a[N],num[N][N],dp[N][N];
//a[N]里面是存数字串
//num[i][j]表示数字串a[N]的第i位到第j位之间的数字串表示的数组
int main(){
int n,m;
while(scanf("%d %d",&n,&m)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
//预处理,计算i到j数字串组成的数字
for(int i=1;i<=n;i++){
num[i][i]=a[i];//只有一个数字
for(int j=i+1;j<=n;j++){
num[i][j]=num[i][j-1]*10+a[j];
}
}
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
dp[0][i]=num[1][i];//无加号时
}
//这里n可以写在m前面。要加一个限制条件n>m,好麻烦,所以m在前且n=m+1
//这里k的取值范围就是m到n,k表示在第k个数后面插入加号
for(int i=1;i<=m;i++)
for(int j=i;j<=n;j++)
for(int k=i;k<=j;k++)
dp[i][j]=min(dp[i][j],dp[i-1][k]+num[k+1][j]);
cout<<dp[m][n]<<endl;
}
}
下面提供2个高精度的AC代码
//By Guo Wei
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
struct BigInt
{
int num[110];
int len;
BigInt operator+(const BigInt & n) { //重载+,使得 a + b在 a,b都是 BigInt变量的时候能成立
int ml = max(len,n.len);
int carry = 0; //进位
BigInt result;
for(int i = 0;i < ml; ++i) {
result.num[i] = num[i] + n.num[i] + carry;
if( result.num[i] >= 10) {
carry = 1;
result.num[i] -= 10;
}
else
carry = 0;
}
if ( carry == 1) {
result.len = ml + 1;
result.num[ml] = 1;
}
else
result.len = ml;
return result;
}
bool operator<(const BigInt & n) {
if( len > n.len )
return false;
else if( len < n.len)
return true;
else {
for(int i = len -1; i >= 0; -- i) {
if( num[i] < n.num[i])
return true;
else if( num[i] > n.num[i])
return false;
}
return false;
}
}
BigInt() {
len = 1;
memset(num,0,sizeof(num));
}
BigInt(const char * n,int L) { //由长度为L的char数组构造大整数。n里面的元素取值范围从 1-9。
memset(num,0,sizeof(num));
len = L;
for(int i = 0; n[i]; ++i)
num[len-i-1] = n[i] - '0';
}
};
ostream & operator <<(ostream & o,const BigInt & n)
{
for(int i = n.len - 1;i >= 0; --i)
o << n.num[i];
return o;
}
const int MAXN = 60;
char a[MAXN];
BigInt Num[MAXN][MAXN];//Num[i][j]表示从第i个数字到第j个数字所构成的整数
BigInt V[MAXN][MAXN]; //V[i][j]表示i个加号放到前j个数字中间,所能得到的最佳表达式的值。
int main()
{
int m,n;
BigInt inf; //无穷大
inf.num[MAXN-2] = 1;
inf.len = MAXN-1;
while(cin >> m ) {
cin >> a+1;
n = strlen(a+1);
for(int i = 1;i <= n; ++i)
for(int j = i;j<= n; ++j) {
Num[i][j] = BigInt(a+i,j-i+1);
}
for(int j = 1; j <= n; ++j) {
V[0][j] = BigInt(a+1,j);
}
for(int i = 1;i <= m; ++i) {
for(int j = 1; j <= n; ++j) {
if( j - 1 < i)
V[i][j] = inf;
else {
BigInt tmpMin = inf;
for(int k = i; k < j; ++k) {
BigInt tmp = V[i-1][k] + Num[k+1][j];
if (tmp < tmpMin)
tmpMin = tmp;
}
V[i][j] = tmpMin;
}
}
}
cout << V[m][n] << endl;
}
return 0;
}
之前讲过string,就用string来写写吧
#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#define ME0(X) memset((X), 0, sizeof((X)))
using namespace std;
const int L=100;
string dp[100][100];
string add(string a,string b)//只限两个非负整数相加
{
string ans;
int na[L]= {0},nb[L]= {0};
int la=a.size(),lb=b.size();
for(int i=0; i<la; i++)
na[la-1-i]=a[i]-'0';
for(int i=0; i<lb; i++)
nb[lb-1-i]=b[i]-'0';
int lmax=la>lb?la:lb;
for(int i=0; i<lmax; i++)
na[i]+=nb[i],na[i+1]+=na[i]/10,na[i]%=10;
if(na[lmax])
lmax++;
for(int i=lmax-1; i>=0; i--)
ans+=na[i]+'0';
return ans;
}
string mins(string a,string b)//判断大小
{
if(a.length()<b.length())
return a;
else if(b.length()<a.length())
return b;
else
return a<b?a:b;
}
int main()
{
int m;
string s;
while(cin >> m >> s)
{
s=" "+s;
int len=s.length();
for(int i=0; i<=len; i++)
dp[i][0]=s.substr(1,i);
for(int j=1; j<=m; j++)
{
for(int i=0; i<=len; i++)
{
for(int x=j; x<i; x++)
{
前x个数和"+"相等时,显然不成立,x个数最多有x-1个"+",所以要单独处理
if(x==j)
dp[i][j]=add(dp[x][j-1],s.substr(x+1,i-x));
// 其他的情况,状态转移方程即可
else
dp[i][j]=mins(dp[i][j],add(dp[x][j-1],s.substr(x+1,i-x)));
}
}
}
cout << dp[len][m] << endl;
}
}
学会程序和算法,走遍天下都不怕
广西桂林