乘积最大
由于题目给定的是m,需要分解成m+1部分的乘积,不难想到乘号刚好是m个,那么该题就转化成了m个乘号的插入方式。
最优子结构分析:
设数字字符串为a1a2…an
m=1 时,一个乘号可以插在a1a2…an中的n-1个位置,这样就得到n-1种乘积:
a1*a2…an, a1a2*a3…an, …, a1a2…a n-1*an
此时的最大值= max { a1*a2…an, a1a2*a3…an, …, a1a2…a n-1*an }
m=2时,两个乘号可以插在a1a2…an中n-1个位置的任两个地方,这样总共会产生
个乘积。把这些乘积分个类,便于观察规律。
Case1: a1a2 …*a n-1*an , a1a2 …*a n-2 a n-1*an , a1*a2 …a n-3 a n-2 a n-1 * an ,
因后一个乘号位置不变,要使这些乘积最大,就要找出在前n-1个数中插入一个乘号的最大值。引入符号F(n-1,1)表示在前n-1个数中插入一个乘号的最大值,则 Case1的最大值为F(n-1, 1) * an
Case2: a1a2 …*a n-2*a n-1 an , a1a2 …*a n-3 a n-2* a n-1 an , a1*a2 …a n-3 a n-2 * a n-1 an ,
因后一个乘号位置不变,要使这些乘积最大,就要找出在前n-2个数中插入一个乘号的最大值。设符号F(n-2)(1)为在前n-2个数中插入一个乘号的最大值,则 Case2的最大值为F(n-2,1) * a n-1 an
同理,Case3的最大值为F(n-3,1) * a n-2 a n-1 an …… Case n-2的最大值为F(2,1) * a3…an
把一段数字串转化为一个整数:
int get(int i,int j) { //将i~j这一段数字转化为一个整数 int ret=0; for(int k=i;k<=j;k++) { ret=ret*10+s[k]-'0'; } return ret; }
状态转移方程推导:
下面以n=9, m=4, s=‘321044105’为例,说明计算过程。
我们引入符号f(i,j)表示从a1~ai中插入j个乘号取得的最大值,g[i][j]表示从ai~aj的子串的数值。则上式可表示为:
F(9,4) = max{f(8,3)*g[9][9], f(7,3)*g[8][9], f(6,3)*g[7][9], f(5,3)*g[6][9], f(4,3)*g[5][9]}
F(8,3) = max{f(7,2)*g[8][8], f(6,2)*g[7][8], f(5,2)*g[6][8], f(4,2)*g[5][8],f(3,2)*g[4][8]}
F(7,3) = max{f(6,2)*g[7][7], f(5,2)*g[6][7], f(4,2)*g[5][7], f(3,2)*g[4][7]}
…………
上面的分析已经看出问题的最优子结构、重复子问题性质。
定义状态:dp[i][j]表示从a1~aj中插入i个乘号取得的最大值
dp[i][j] = max{ dp(i-1, k)*get(k+1, j) } (1<=i<=m, i+1<=j<=n, i<=k<j )
i的取值范围为:乘号个数
j的取值范围为:乘号数i加1 ~ 数字串总个数
k的取值范围为:乘号个数~右边界j减去1
上式的边界条件是什么?
dp[0][i] = get(1, i) (1<=i<=n)
我们要求的问题的最优解是dp[m][n]
#include <bits/stdc++.h> using namespace std; long long n,k,dp[45][45],m; char a[45]; long long num(long long x,long long y) { long long sum=0; for(long long i=x;i<=y;i++) { sum=sum*10+a[i]-'0'; } return sum; } int main() { scanf("%lld%lld",&m,&n); scanf("%s",a+1); for(int i=1;i<=m;i++) { dp[0][i]=num(1,i); } for(long long i=1;i<=n;i++) { for(long long j=i+1;j<=m;j++) { for(long long k=i;k<j;k++) { dp[i][j]=max(dp[i-1][k]*num(k+1,j),dp[i][j]); } } } printf("%lld\n",dp[n][m]); }