区间划分 DP:dp[j][i] 表示前 i 位放 j 个乘号的最大乘积,转移枚举最后一个乘号的位置,大整数用 Python 原生 int。
OJ: roj
题目 ID: 3501
难度:普及+/提高-
标签:动态规划区间 DP大整数python
创建: 2026-10-02 03:23
更新: 2026-10-10 11:30
形式化题目
给定一个长度为
题面样例:3*12=36 和 31*2=62 两种分法,最大值为
正解
思路
朴素做法:枚举
优化推导:把这部分公共子问题提出来,定义
对"最后一个乘号放在哪"做决策:设它放在位置
两个下界
- 最优子结构:若最优方案中最后一个乘号在
之后,而前 位内部的放法不是最优,换成更优放法只会让总乘积更大(各段值均为正整数),矛盾; - 无后效性:
只由"前 位、 个乘号"决定,末段取值与前缀内部怎么分无关。
边界:
还有一个本题特有的便利:int 天然支持任意精度,直接乘即可。
样例 DP 表:— 表示前
| 1 | 12 | 123 | 1231 | |
| — | 2 | 36 | 372 | |
| — | — | — | 62 |
看一轮具体转移:12 再乘 31 更划算。最后一格 31*2=62(同一乘积的镜像切分)一致。
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 十进制大整数(little-endian),只做本题需要的加法/乘法/比较
struct BigInt {
vector<int> d;
BigInt() { d.push_back(0); }
BigInt(ll x) { d.clear(); if (x == 0) d.push_back(0); else while (x) { d.push_back(x % 10); x /= 10; } }
BigInt(const string& s) { d.clear(); for (int i = (int)s.size() - 1; i >= 0; i--) d.push_back(s[i] - '0'); if (d.empty()) d.push_back(0); trim(); }
void trim() { while (d.size() > 1 && d.back() == 0) d.pop_back(); }
bool is_zero() const { return d.size() == 1 && d[0] == 0; }
int cmp(const BigInt& o) const {
if (d.size() != o.d.size()) return d.size() < o.d.size() ? -1 : 1;
for (int i = (int)d.size() - 1; i >= 0; i--)
if (d[i] != o.d[i]) return d[i] < o.d[i] ? -1 : 1;
return 0;
}
bool operator<(const BigInt& o) const { return cmp(o) < 0; }
string str() const { string r; for (int i = (int)d.size() - 1; i >= 0; i--) r += char('0' + d[i]); return r; }
BigInt operator*(const BigInt& o) const {
BigInt r; r.d.assign(d.size() + o.d.size(), 0);
for (size_t i = 0; i < d.size(); i++)
for (size_t j = 0; j < o.d.size(); j++)
r.d[i + j] += d[i] * o.d[j];
for (size_t i = 0; i + 1 < r.d.size(); i++) { r.d[i + 1] += r.d[i] / 10; r.d[i] %= 10; }
r.trim(); return r;
}
};
int n, k;
string s;
BigInt dp[8][45]; // dp[j][i]:前 i 个数字插 j 个乘号的最大乘积
BigInt substr_num(int l, int r) { // s[l..r) 的数字(0 基,r 不含)
return BigInt(s.substr(l, r - l));
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k >> s;
for (int i = 1; i <= n; i++)
dp[0][i] = substr_num(0, i);
for (int j = 1; j <= k; j++) {
for (int i = j + 1; i <= n; i++) {
BigInt best(0);
for (int t = j; t < i; t++) {
BigInt cand = dp[j - 1][t] * substr_num(t, i);
if (best < cand) best = cand;
}
dp[j][i] = best;
}
}
cout << dp[k][n].str() << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 03:23
# update_at: 2026-10-02 03:23
import sys
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
n = int(next(data))
k = int(next(data))
s = next(data).decode()
# dp[j][i]:前 i 个数字之间插 j 个乘号能得到的最大乘积
dp: list[list[int]] = [[0] * (n + 1) for _ in range(k + 1)]
dp[0] = [0] + [int(s[:i]) for i in range(1, n + 1)] # 不放乘号:整个前缀自己就是一个数
for j in range(1, k + 1):
# 枚举最后一个乘号的位置:插在第 t 位后面,前 t 位放 j-1 个乘号,s[t:i] 自成一段
for i in range(j + 1, n + 1):
dp[j][i] = max(dp[j - 1][t] * int(s[t:i]) for t in range(j, i))
print(dp[k][n])
if __name__ == "__main__":
solve()复杂度
- 时间:状态
个,每个转移枚举 个位置,总转移 次,每次含一次不超过 的大整数乘法(Python 原生支持,成本可忽略)。实测 10 组官方数据每组约 17ms。 - 空间:
表 。
朴素枚举则需要约
总结
本题是经典的"数字串插乘号"区间划分 DP:状态