设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。
同时,为了帮助正确理解题意,举如下的一个例子:
有一个数字串:312, 当N=3,K=1时会有以下两种分法:
312=36
312=62
这时,符合题目要求的结果是:312=62
再如 n = 310143
310143 = 0
310143 = 129
310143 = 126
310143 = 1290
310143 = 1260
310143 = 0
310143 = 372
31014*3 = 3720
最大乘积为3720
该题用动态规划做分析:
设有字符串为a1a2a3…an
当k=1时,最大值为max{a1a2a3…an, a1a2a3a4…an,a1a2a3*a4a5…an,…,a1a2a3…*an};
当k=2时,最大值为max{a1a2a3…a(n-2)*a(n-1)*an,a1a2a3…a(n-3)a(n-2)a(n-1)an,…,a1a2a3a4…an}
引入记号f(i,j,s)表示从i到j,s个乘号取得的最大值,g(i,j)表示从i到j的数字队列(字符变成整数)则:
k=1时,f(1,n,1)=max{g(1,1)*g(2,n), g(1,2)*g(3,n), …, g(1,n-1)*g(n,n)};
k=2时,f(1,n,2)=max{f(1, n-1, 1)*g(n,n), f(1, n-2, 1)*g(n-1, n), …,f(1, 2, 1)*g(3, n)};
由此推出
f(1,n,k) = max{f(1, n-1, k-1)*g(n,n), f(1, n-2, k-1)*g(n-1,n), …,f(1, k, k-1)*g(k+1, n)};
我们将f(1,j,s)化简成f(j, s)则转移方程为:
f(n,k) = max{f(n-1, k-1)*g(n,n), f(n-2,k-1)*g(n-1, n), …, f(k, k-1)*g(k+1,n)}
#include <stdio.h>
#include <cstring>
char str[50];
long long f[50][50];
// f[k][n]表示k个乘号,长度为n的最大乘积
long long g(int start, int end) {
long long s = 0, t = 1;
for (int i = end; i >= start; i--) {
s += (str[i]-'0') * t;
t *= 10;
}
return s;
}
int main() {
int n, k, mx;
scanf("%d %d", &n, &k);
scanf(" %s", str); // 下标从0开始 123456
for (int i = 0; i < n; i++) f[0][i] = g(0,i); // 乘号为0个时的数字
for (int i = 1; i <= k; i++) {
for (int j = i; j < n; j++) { // 从i开始是因为i个乘号至少要i+1个字符,因为是从0开始的到i有i+1个字符
mx = f[i-1][j-1] * g(j,j); // j表示字符长度j+1以j结尾 i=1,j=4 f[0][3]*g(4,4)开始 12345 * 6
for (int h = j-1; h >= i; h--) { // 乘号枚举的范围 i ~ j 因为j找过来所以 h = i ~ j-1 乘号枚举再h下标字符的后面 1234 * 56
if (f[i-1][h-1] * g(h,j) > mx) {
mx = f[i-1][h-1] * g(h,j); // 把字符串分解找到最大的那个乘积 也就是在0~h-1里有i-1个乘号再乘str[h~j]
}
}
f[i][j] = mx;
}
}
printf("%lld\n", f[k][n-1]);
return 0;
}