AcWing 计数类DP——900. 整数划分

原题链接:

900. 整数划分 - AcWing题库

题解:

完全背包问题变形:

采用完全背包的思想求解:把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)

初始化:

求最大值时,当都不选时,价值显然是 0;
而求方案数时,当都不选时,方案数是 1(即前 i 个物品都不选的情况也是一种方案),所以需要初始化为 1,也即f[0][0]=1。状态计算:

这里为啥i从0开始而不是从1开始呢,f[0][0]明明是非法含义呀?背包问题里面,恰好等于j和,不超过j的状态表示有什么不同呢?

这是因为一般情况下两种方式用哪种都是可以的,可以随意选择。主要区别在于边界的初始化,和恰好是j的状态表示只有f[0][0]这个边界是合法的,其他边界状态均不合法;和不超过j的状态表示一般所有边界状态都是合法的。

状态计算: 

代码:
二维:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, f[N][N];

int main() {
	cin >> n;

	f[0][0] = 1;
	for (int i = 1;i <= n;i++) {
		for (int j = 0;j <= n;j++) {
			f[i][j] = f[i - 1][j] % mod;
			if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
		}
	}
	cout << f[n][n];
}
 一维:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, f[N];

int main() {
	cin >> n;

	f[0] = 1;
	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];
}
另一种DP方法:
思路:

从数量的角度出发,例如5的划分可以表示为所有总和为5,数量恰好是1~5个数的和的所有方案。

这里的状态计算(集合划分)也是一个难点。 

代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10, mod = 1e9 + 7;
int n, f[N][N], res;

int main() {
	cin >> n;

	f[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;
		}
	}
	for (int j = 1; j <= n; j++)
		res = (res + f[n][j]) % mod;

	cout << res << endl;
}
dfs暴搜(时间复杂度较高无法通过,仅作为参考):

需要划分为4种情况考虑

#include<iostream>
using namespace std;

int dfs(int n, int m)
{
	if (n == 1 || m == 1)//两者任意一个是1时,划分个数都是1
		return 1;
	else if (n < m)//例如对3进行划分成6份根本不现实,最多划分为3个1
		return dfs(n, n);
	else if (n == m)//例如3划分为不超过3个数的划分,其中一种必是3个1
		return 1 + dfs(n, n - 1);
	else
		return dfs(n, m - 1) + dfs(n - m, m);
	//根据划分的数中最小数是否为1,分为2种情况:
	//①划分的数中最小数为1:case1(8,3)等价于case1(7,2),相当于去掉了1,个数减1,总和减1
	//②划分的数中最小数大于1:case2(8,3)等价于case2(8-3,3),相当于划分的数中每个数都减去了1,因为原始值都大于1,所以总数不变,总和需要减去个数*1
}

int main(void)
{
	int n;
	cin >> n;
	cout << dfs(n, n);//表示n的不超过n个数的划分
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值