第十届蓝桥杯决赛C++B组试题B:质数拆分(记忆化搜索)

正确答案是55965365465060!

在某一篇博客上说这一题的答案是1这显然是理解错了题意!

先看原题:
在这里插入图片描述
题意分析:一开始写这一道题的时候以为题意是让把2019分解为两个不相等的素数之和,但是我仔细研读题意后发现正确题意是把2019分解为很多个不相等是素数之和例如可以分解为:3 + 5 + 2011等等。

这一题使用了记忆化搜索,记忆化搜索是DP的一类,不了解的同学先简单的来看一下记忆化搜索的概念:
在这里插入图片描述
我也是前几天才学习了记忆化搜索,刚开始学习的时候在想为什么把记忆化搜索归为DP而不是DFS,直到今天在复习质数拆分这一题时才突然悟了:记忆化搜索是利用递归来实现状态转移,并避免重复运算。
来看一下这一题的代码,我会在后面解释代码中的一些不好理解的地方:

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;

const int N = 3000;

//#####当在第 i 个位置 使得总和为 j 的选法的总数  
LL f[N][N];//注释1  
bool check[N];
vector<int> prime;

void make_prime()//注释2 
{
	for(int i = 2; i <= 2019; i++)
	{
		if(!check[i]) prime.push_back(i);
		
		for(int j = 0; j < prime.size() && i * prime[j] <= 2019; j++)
		{
			check[i * prime[j]] = true;
			if(!(i % prime[j])) break;
		}
	}
}

//##利用递归来实现状态转移  
LL DP(int pos,int sum)//求出在位置为pos,总和为sum时对应的总选法   
{	
	if(f[pos][sum] != -1) return f[pos][sum];//记忆化搜索:将已经计算的 结果保存下来,避免重复运算   
	if(sum == 2019) return 1;
	if(pos >= prime.size() || sum > 2019) return 0;//剪枝 
	
	LL ans = 0;
	//注释3 
	ans += DP(pos + 1,sum);//不要这个位置上的素数  
	ans += DP(pos + 1,sum + prime[pos]);//要当前这个位置上的素数   
	f[pos][sum] = ans;//#基于递归的状态转移  
	return ans; 
}

int main()
{
	make_prime();//筛    
	
	memset(f,-1,sizeof f);
	
	LL ans = DP(0,0);
	
	cout << ans << endl;
	return 0;
}

DP问题需要注意的最重要的两点无非就是:状态表示和状态转移(计算)。
在这里插入图片描述
注释1:这一题的状态表示f[i][j]可以理解为:对于素数数组中的位置i,目前总和为j的选法的总数(这里可能不太好理解建议多敲几遍代码,结合代码来理解)
注释2:线性筛,给大家推荐一篇博客线性筛算法
注释3:这里主要解释一下 ans 为什么同时需要加上两个函数的返回值:我们先明确一下DP函数的作用:计算出在某个素数数组的位置pos上当总和为sum时对应的总选法 ,举个例子:我们先截取一段素数数组:

2 3 5 7 11 13 17 19 23 29 

在某种情况下递归到达了素数数组的第5个位置即到达了素数13,现在面临着两个问题:
1.不让13参与构成组成2019的一部分:

ans += DP(pos + 1,sum);//不要这个位置上的素数

2.让13参与构成组成2019的一部分

ans += DP(pos + 1,sum + prime[pos]);//要当前这个位置上的素数 

其实每当到达递归的一个位置时都要面临一个问题:是否选择prime[pos],而对于每一种选择都可能出现多种或0种构成2019的方式,比如,有可能在本层选择13的情况下无论后面怎么选都凑不出2019(我是说如果),这时DP(pos + 1,sum + prime[pos])的值就为 0

至此,这篇文章就先结束吧,如果有解释不清楚的地方或不恰当的地方就留言在评论区吧,我会进一步完善文章的。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值