【洛谷】P1018 乘积最大(动态规划+高精度)

题目链接

题目描述

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

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

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

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

1、3×12=36 2、31×2=62

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

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

输入输出格式

输入格式:

程序的输入共有两行:

第一行共有2个自然数N,K(6≤N≤40,1≤K≤6)

第二行是一个长度为N的数字串。

输出格式:

结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。

输入输出样例

输入样例#1: 复制

4  2
1231

输出样例#1: 复制

62

预处理中可以先把字符串中第i至j位的数都算出来,用num[i][j]表示。

用dp[i][j]表示前i个数插入j个乘号所得的最大乘积,递推关系时,dp[i[[j]与dp[k][j-1]有关联,其中k的范围是[j,i-1],表示最后一个乘号的位置(第k个数与第k+1个数的间隔)。

注意精度,用__int128处理的时候精度还是不够,此处可参考上一篇博客介绍的方法C++高精度模板

80分代码:

#include<iostream>
#include<sstream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
using namespace std;

__int128 dp[42][7];//dp[i][j]表示前i个数插入j个乘号的最大乘积 (从1开始) 
__int128 num[42][42];//num[i][j]表示字符串中第i位至第j为组成的数(从1开始) 
inline void output(__int128 a)
{
	if(a>9) output(a/10);
	putchar(a%10+'0');
}
int main()
{
	int N,K; 
	string str; 
	cin>>N>>K>>str;
	memset(dp,0,sizeof(dp));
	memset(num,0,sizeof(num));
	
	for(int i=1;i<=N;i++)
	{
		for(int j=i;j<=N;j++)
		num[i][j]=num[i][j-1]*10+str[j-1]-'0';
	}
	
	for(int i=1;i<=N;i++) dp[i][0]=num[1][i]; 
	
	for(int j=1;j<=K;j++)//乘号个数
	    for(int i=2;i<=N;i++)//前i个数
	        for(int k=j;k<=i-1;k++)//最后一个乘号放在第k个数后第k+1个数前 
		        dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i]);
	     
        output(dp[N][K]);
}

AC代码:

#include<iostream>
#include<sstream>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
using namespace std;
struct Wint:vector<int>//用标准库vector做基类,完美解决位数问题,同时更易于实现
{
    //将低精度转高精度的初始化,可以自动被编译器调用
    //因此无需单独写高精度数和低精度数的运算函数,十分方便
    Wint(int n=0)//默认初始化为0,但0的保存形式为空
    {
        push_back(n);
        check();
    }
    Wint& check()//在各类运算中经常用到的进位小函数,不妨内置
    {
        while(!empty()&&!back())pop_back();//去除最高位可能存在的0
        if(empty())return *this;
        for(int i=1; i<size(); ++i)
        {
            (*this)[i]+=(*this)[i-1]/10;
            (*this)[i-1]%=10;
        }
        while(back()>=10)
        {
            push_back(back()/10);
            (*this)[size()-2]%=10;
        }
        return *this;//为使用方便,将进位后的自身返回引用
    }
};
//输入输出
istream& operator>>(istream &is,Wint &n)
{
    string s;
    is>>s;
    n.clear();
    for(int i=s.size()-1; i>=0; --i)n.push_back(s[i]-'0');
    return is;
}
ostream& operator<<(ostream &os,const Wint &n)
{
    if(n.empty())os<<0;
    for(int i=n.size()-1; i>=0; --i)os<<n[i];
    return os;
}
//比较,只需要写两个,其他的直接代入即可
//常量引用当参数,避免拷贝更高效
bool operator!=(const Wint &a,const Wint &b)
{
    if(a.size()!=b.size())return 1;
    for(int i=a.size()-1; i>=0; --i)
        if(a[i]!=b[i])return 1;
    return 0;
}
bool operator==(const Wint &a,const Wint &b)
{
    return !(a!=b);
}
bool operator<(const Wint &a,const Wint &b)
{
    if(a.size()!=b.size())return a.size()<b.size();
    for(int i=a.size()-1; i>=0; --i)
        if(a[i]!=b[i])return a[i]<b[i];
    return 0;
}
bool operator>(const Wint &a,const Wint &b)
{
    return b<a;
}
bool operator<=(const Wint &a,const Wint &b)
{
    return !(a>b);
}
bool operator>=(const Wint &a,const Wint &b)
{
    return !(a<b);
}
//加法,先实现+=,这样更简洁高效
Wint& operator+=(Wint &a,const Wint &b)
{
    if(a.size()<b.size())a.resize(b.size());
    for(int i=0; i!=b.size(); ++i)a[i]+=b[i];
    return a.check();
}
Wint operator+(Wint a,const Wint &b)
{
    return a+=b;
}
//减法,返回差的绝对值,由于后面有交换,故参数不用引用
Wint& operator-=(Wint &a,Wint b)
{
    if(a<b)swap(a,b);
    for(int i=0; i!=b.size(); a[i]-=b[i],++i)
        if(a[i]<b[i])//需要借位
        {
            int j=i+1;
            while(!a[j])++j;
            while(j>i)
            {
                --a[j];
                a[--j]+=10;
            }
        }
    return a.check();
}
Wint operator-(Wint a,const Wint &b)
{
    return a-=b;
}
//乘法不能先实现*=,原因自己想
Wint operator*(const Wint &a,const Wint &b)
{
    Wint n;
    n.assign(a.size()+b.size()-1,0);
    for(int i=0; i!=a.size(); ++i)
        for(int j=0; j!=b.size(); ++j)
            n[i+j]+=a[i]*b[j];
    return n.check();
}
Wint& operator*=(Wint &a,const Wint &b)
{
    return a=a*b;
}
Wint dp[42][7];//dp[i][j]表示前i个数插入j个乘号的最大乘积 (从1开始) 
Wint num[42][42];//num[i][j]表示字符串中第i位至第j为组成的数(从1开始) 
int main()
{
	int N,K; 
	string str; 
	cin>>N>>K>>str;
	memset(dp,0,sizeof(dp));
	memset(num,0,sizeof(num));
	
	for(int i=1;i<=N;i++)
	{
		for(int j=i;j<=N;j++)
		num[i][j]=num[i][j-1]*10+str[j-1]-'0';
	}
	
	for(int i=1;i<=N;i++) dp[i][0]=num[1][i]; 
	
	for(int j=1;j<=K;j++)//乘号个数
	    for(int i=2;i<=N;i++)//前i个数
	        for(int k=j;k<=i-1;k++)//最后一个乘号放在第k个数后第k+1个数前 
		        dp[i][j]=max(dp[i][j],dp[k][j-1]*num[k+1][i]);
	     
        cout<<dp[N][K];
}

猜你喜欢

转载自blog.csdn.net/qq_40889820/article/details/82154751