动态规划(三):区间DP, 计数类DP
动态规划:区间DP(石子合并) 、 计数类DP(整数划分)
区间DP
AcWing 282. 石子合并
设有 堆石子排成一排,其编号为 。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 堆石子分别为 1 3 5 2
, 我们可以先合并 堆,代价为 ,得到 4 5 2
, 又合并 堆,代价为 ,得到 9 2
,再合并得到 ,总代价为 ;
如果第二步是先合并 堆,则代价为 ,得到 4 7
,最后一次合并代价为 ,总代价为 。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 表示石子的堆数 。
第二行 个数,表示每堆石子的质量(均不超过 )。
输出格式
输出一个整数,表示最小代价。
数据范围
输入样例:
4
1 3 5 2
输出样例:
22
code:
#pragma once
#include <iostream>
using namespace std;
const int N = 1010;
int f[N][N], n, prefix_sum[N];//f表示状态,n表示个数,prefix_sum表示前缀和
// f[i][j] 表示所有将第i堆石子到第j堆石子合并成一堆石子的方式的集合, 属性值是集合的最小值
void combine_rock_piles()
{
// 处理输入
cin >> n;
for (int i = 1; i <= n; ++i) cin >> prefix_sum[i];
// 处理前缀和
for (int i = 1; i <= n; ++i) prefix_sum[i] += prefix_sum[i - 1];
// 按照长度从小到大枚举状态
for (int len = 2; len <= n; ++len) // 直接从区间为2开始枚举,因为1不需要合并,代价为0,而f[i][i]默认就是零
//枚举起点
for (int i = 1; i + len - 1 <= n; ++i)
{
int l = i, r = i + len - 1; //初始化区间左右端点值,枚举区间,这里的区间都是大于1的,区间为1的默认为0
f[l][r] = 1e8; //算f[i][j]之前初始化成正无穷, 要不然就全部是零了
for (int k = l; k < r; ++k)
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + prefix_sum[r] - prefix_sum[l - 1]);
}
cout << f[1][n] << endl;
}
计数类DP
AcWing 900. 整数划分
一个正整数 可以表示成若干个正整数之和,
形如:。
我们将这样的一种表示称为正整数 的一种划分。 现在给定一个正整数 ,请你求出共有多少种不同的划分方法。
输入格式
共一行,包含一个整数 。
输出格式
共一行,包含一个整数,表示总划分数量。 由于答案可能很大,输出结果请对 取模。
数据范围
1≤n≤1000
输入样例:
5
输出样例:
7
解法一:
转化为完全背包问题求解, 表示从 中选总体积恰好为 的选法数量。
状态计算:
因此,
#pragma once
#include <iostream>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N];
// f[i][j]表示从1 ~ i物品中所有选取体积恰好为j得选法得集合
// 集合属性值是所有得方案数加在一起
// f[i][j] = f[i - 1][j] + f[i][j - i]
void integer_division()
{
int n;
cin >> n;
f[0] = 1; //出了f[0]之外都是0, 这是初始化
for (int i = 1; i <= n; ++i)
for (int j = i; j <= n; ++j)
f[j] = (f[j] + f[j - i]) % mod;
cout << f[n] << endl;
}
解法二:
表示所有总和是 ,并且恰好表示成 个数的和的方案数。
状态计算:
划分为最小值为 时,方案数为 ;最小值大于 时,方案数为 .
则 ,最终结果是 .
#pragma once
#include <iostream>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N][N];
void integer_division2()
{
int n;
cin >> n;
f[0][0] = 1; //总和是0,并且用0个数表示的方案数目为1
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= i; ++j)
f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
int res = 0;
for (int j = 1; j <= n; ++j) res = (res + f[n][j]) % mod;
cout << res << endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!