蓝桥杯2019第十届国赛_质数拆分(动态规划_01背包)

蓝桥杯2019第十届国赛_质数拆分(动态规划_01背包)

题目:
将 20192019 拆分为若干个两两不同的质数之和,一共有多少种不同的方法?
注意交换顺序视为同一种方法,例如 2 + 2017 = 20192+2017=2019 与 2017 + 2 = 20192017+2=2019 视为同一种方法。

动态规划第一步,确定dp数组的下标:

dp[i][j]从第1到第i个质数中选,满足他们的和等于j的情况数

其中,我们把[2, 2019)这个区间内的质数找出来,并放在zhi[]这个数组中:

vector<int> zhi{0};
zhi.push_back(2);
for (int i = 3; i < 2019; i++) {
	if (ifPrime(i)) zhi.push_back(i);
}

注意,zhi[0]存的数仅仅为一个占位符,方便后续我们进行下标操作,真正的质数从下标1开始。为什么取右开区间,因为既然要分解,2019肯定不能分解成自己和其它质数。

ifPrime(n)函数用来判断n是不是质数,具体不再赘述。

第二步,确定状态转移方程,也就是递推公式。

  • 如果zhi[i] >= j,则当前数j可能能被zhi[i]与其它质数分解,总方案数为用zhi[1]…zhi[i - 1]分解j的方案数 + 用zhi[1]…zhi[i - 1]分解j - zhi[i]的方案数(取=号是当x被拆出质数y后,刚好x - y也是一个质数的情况)
  • 其它情况,则当前数j不可能被zhi[i]与其它质数分解,总方案数为用zhi[1]…zhi[i - 1]分解j的方案数

第三步,初始化dp数组

我们把dp[0][0]赋值为1,这是我们进行递推操作的重要基础。

大致可以理解成0的质数拆分就是不去拆它,也算是一种情况。如果大家有更好的理解,欢迎交流。

更新:当x被拆出质数y后,如果x - y也是一个质数,那么这时x - y不再拆分也算是一种情况,所以看起来是x - y自己把自己拆分后dp[k][0]是1。

第四步,确定遍历顺序

由递推式我们知道,从左到右,从上到下就能完成遍历。但是如果我们使用压缩空间的滚动数组法,滚动的过程中反而会覆盖需要的数据,所有滚动数组法是每行从右到左遍历。

代码实现

#include 
using namespace std;
typedef long long ll;

bool ifPrime(int n) {
	if (n == 1) return false;
	for (int i = 2; i <= sqrt(n); i++) {
		if (n % i == 0) return false;
	}
	return true;
}

int main() {
	vector<int> zhi{0};
	zhi.push_back(2);
	for (int i = 3; i < 2019; i++) {
		if (ifPrime(i)) zhi.push_back(i);
	}
	
	vector<vector<ll> > dp(zhi.size(), vector<ll>(2020, 0));
	dp[0][0] = 1;
	for (int i = 1; i < dp.size(); i++) {
		for (int j = 0; j < dp[i].size(); j++) {
			dp[i][j] = dp[i - 1][j];
			if (j >= zhi[i]) dp[i][j] += dp[i - 1][j - zhi[i]];
		}
	}
	cout << dp.back().back();
	return 0;
}

你可能感兴趣的:(刷题之路,蓝桥杯,动态规划)