【题目链接】
ybt 1912:【00NOIP普及组】乘积最大
ybt 1821:【00NOIP提高组】乘积最大
OpenJudge NOI 2.6 8782:乘积最大
洛谷 P1018 [NOIP2000 提高组] 乘积最大
【题目考点】
1. 动态规划:区间动规
2. 高精度
【解题思路】
0. 分析结果的最大值
在一个最多40位数中,插入最多6个乘号,有最多7个数字相乘。
可以肯定结果会超出long long可以表示的范围,因此本问题需要使用高精度数字。
1. 处理得到第i位到第j位截取出的数字
预先处理,num[i][j]
(高精度数字)表示该数字从第i位到第j位截取出的数字。
将输入的数字m取第i位到第j位填充到另一个高精度数字的数组中,即可获得该数字。
2. 状态定义
集合:在数字中插入乘号的方案
限制:看前几位数字,使用几个乘号
属性:乘积
条件:最大
统计量:乘积
状态定义:dp[i][j]
:(高精度数字)在前i位数字中插入j个乘号能得到的最大乘积。
初始状态:dp[i][0]
:前i位数字中插入0个乘号,乘积就是这i位数字。dp[i][0] = num[1][i]
3. 状态转移方程
集合:在前i位数字中插入j个乘号的方案
分割集合:根据第j个乘号插入位置的不同情况分割集合
因为第j个乘号前面还有j-1个乘号,最密集插入乘号,每两个乘号间有1位数字,第j-1个乘号会在第j-1位数字的后面。第j个乘号可能的最靠前的位置,为第j位数字的后面。
第j个乘号可以取到的最靠后的位置,为第i-1位数字的后面。
假设第j个乘号在第h位数字的后面,那么第j乘号后面的数字,为第h+1位到第i位组成的数字。
h从j遍历到i-1,先求出前h位数字中插入j-1个乘号能得到的最大乘积(dp[h][j-1]
),再乘以第h+1位到第i位组成的数字(num[h+1][i]
),得到前i位数字插入j个乘号的最大乘积:dp[i][j] = dp[h][j-1]*num[h+1][i]
(高精乘高精)。对所有可能的情况取最大值(高精度数字比较)。
本题将一段数字分为前半段(前h位数字插入j-1个乘号)与后半段(数字num[h+1][i]
),类似于区间动规中遍历断点,将一个大区间拆分为小区间的思想。
【题解代码】
解法1:高精度 区间动规
#include<bits/stdc++.h>
using namespace std;
#define N 50
struct HPN
{
int a[N];
HPN()
{
memset(a, 0, sizeof(a));
}
HPN(string s)//字符串初始化高精度数字
{
memset(a, 0, sizeof(a));
a[0] = s.length();
for(int i = 1; i <= a[0]; ++i)
a[i] = s[a[0]-i] - '0';
}
int& operator [] (int i)//左值 注意返回值为引用
{
return a[i];
}
HPN sub(int i, int j)//截取本数字低位第i位到高位第j位构成一个新的数字
{
int ai = 0;
HPN r;
for(int k = i; k <= j; ++k)
r[++ai] = a[k];
r[0] = ai;
return r;
}
HPN operator * (HPN b)//高精乘高精
{
HPN r;
int c;
for(int i = 1; i <= a[0]; ++i)
{
c = 0;
for(int j = 1; j <= b[0]; ++j)
{
r[i+j-1] += a[i]*b[j] + c;
c = r[i+j-1] / 10;
r[i+j-1] %= 10;
}
r[i+b[0]] += c;
}
int ri = a[0] + b[0];
while(r[ri] == 0 && ri > 1)
ri--;
r[0] = ri;
return r;
}
void show()
{
for(int i = a[0]; i >= 1; --i)
cout << a[i];
cout << endl;
}
};
HPN dp[N][N], num[N][N];//dp[i][j]:在前i位数字中插入j个乘号能得到的最大乘积。
string s;//数字串
int n, k;
HPN Max(HPN a, HPN b)//比较两个高精度数字,返回较大值
{
if(a[0] > b[0])
return a;
else if(a[0] < b[0])
return b;
else
{
for(int i = a[0]; i >= 1; --i)
{
if(a[i] > b[i])
return a;
else if(a[i] < b[i])
return b;
}
return a;//相等的情况
}
}
void initNum(HPN m)//初始化num数组
{
for(int i = 1; i <= n; ++i)//从高位数第i位为从低位数第n-i+1位
for(int j = i; j <= n; ++j)//高位数第j位为从低位数第n-j+1位
num[i][j] = m.sub(n-j+1, n-i+1);//num[i][j]:表示该数字从高位数第i位到第j位截取出的数字。n-j+1是低位,n-i+1是高位,sub成员函数先传低位,再传高位,截取数字。
}
int main()
{
cin >> n >> k >> s;//s为数字串
initNum(HPN(s));//用数字串构造出高精度数字,初始化num数组
for(int i = 1; i <= n; ++i)//初始状态
dp[i][0] = num[1][i];//前i位数字中插入0个乘号,乘积就是这i位数字
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= k; ++j)
for(int h = j-1; h < i; ++h)
dp[i][j] = Max(dp[i][j], dp[h][j-1]*num[h+1][i]);//高精乘高精,两个高精度数字取最大值
dp[n][k].show();
return 0;
}