Loading

动态规划(三):区间DP, 计数类DP

动态规划:区间DP(石子合并) 、 计数类DP(整数划分)

区间DP

AcWing 282. 石子合并

设有 N 堆石子排成一排,其编号为 123N

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 12 堆,代价为 4,得到 4 5 2, 又合并 12 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24

如果第二步是先合并 23 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式

第一行一个数 N 表示石子的堆数 N

第二行 N 个数,表示每堆石子的质量(均不超过 1000)。

输出格式

输出一个整数,表示最小代价。

数据范围

1N300

输入样例:

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. 整数划分

一个正整数 n 可以表示成若干个正整数之和,
形如:n=n1+n2++nkn1n2nk,k1
我们将这样的一种表示称为正整数 n 的一种划分。 现在给定一个正整数 n,请你求出n共有多少种不同的划分方法。

输入格式
共一行,包含一个整数 n

输出格式
共一行,包含一个整数,表示总划分数量。 由于答案可能很大,输出结果请对 109+7 取模。

数据范围
1≤n≤1000

输入样例:

5

输出样例:

7

解法一:
转化为完全背包问题求解, f[i][j] 表示从 1 i 中选总体积恰好为 j 的选法数量。
状态计算:
f[i][j]=f[i1[j]+f[i1][j1]+f[i1][j2]++f[i1][jis]
f[i][ji]=              f[i1][j1]+f[i1][j2]++f[i1][jis](sisj
因此,f[i][j]=f[i1][j]+f[i][ji]

#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;
}

解法二:
f[i][j] 表示所有总和是 i,并且恰好表示成 j 个数的和的方案数。
状态计算:
划分为最小值为 1 时,方案数为 f[i1][j1];最小值大于 1 时,方案数为 f[ij][j].
f[i][j]=f[i1][j1]+f[ij][j],最终结果是 f[n][1]+f[n][2]++f[n][n].

#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;
}
posted @   Christopher_James  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示