DP练习题:求解幸运数问题(递归思想,动规解决)

【问题描述】
小明同学学习了不同的进制之后,拿起了一些数字做起了游戏。小明同学知道,在日常生活中我们最常用的是十进制数,而在计算机中,二进制数也很常用。现在对于一个数字x,小明同学定义出了两个函数f(x)和g(x)。 f(x)表示把x这个数用十进制写出后各个数位上的数字之和。如f(123)=1+2+3=6。 g(x)表示把x这个数用二进制写出后各个数位上的数字之和。如123的二进制表示为1111011,那么,g(123)=1+1+1+1+0+1+1=6。 小明同学发现对于一些正整数x满足f(x)=g(x),他把这种数称为幸运数,现在他想知道,小于等于n的幸运数有多少个?

【输入】
21

【输出】
3
【解释】
21以内的幸运数有1, 20, 21.所以结果输出 3 个

【解题思路】
本题是一道dp题,难倒不难,但是状态转移也并不是那么好想出来的。做dp或者递归题,首先还是手动把情况列一列,只有自己列出来了,问题的规律才好找,否则,规律去哪找?

我是这样想的,这一题经过思考我觉得不能直接把递归函数 g(n) 设为前n个数中幸运数的个数,因为这样并不好找子问题,感觉并没有递归结构。又因为,二进制0101本身有很强的递归性,所以我觉得将g(n)按照题意设为将整数n写为二进制数之后各位上的值之和
DP练习题:求解幸运数问题(递归思想,动规解决)_第1张图片
那么把基础情况列出来之后会大概看到上图的情况。这时候,我很快反应过来,因为偶数的二进制数都是以0结尾的,所以奇数的递推式其实就出来了,g(n) = g(n - 1) + 1.因为偶数都是0结尾,所以奇数直接在它的前一个数(肯定是偶数)的基础上加1就行了,不存在进位的问题!

但是偶数我就感觉很麻烦了,因为偶数存在进位问题啊,它的g值不能仅仅看前一个奇数的g值,那么偶数的递推式是什么呢?我在这里陷入了思考。。。

后来我才灵光一闪,将二进制数(如:110)看成一个字符串,因为偶数末尾都是0嘛,只可能有一种情况,那么如果你知道了前面的11,不就知道了110了吗?那么如何知道11的g值呢?哦哦哦,二进制中除以2就行了嘛,哈哈,所以就推导出来了
g(n) = g(n / 2); (n为偶数)

所以在推导出二进制数公式之后,十进制数公式就更好推导了,方法一样,f(n)表示n写成十进制数各位上的数相加的和
f(n) = f(n / 10) + n % 10(ps: 根本不需要循环去拆分每一位,直接递归搞定)

那么此题用递归完成完全可以,为了优化,我用了动规实现:

#include
using namespace std;

int n;
int dp[100002];
int a[100002];			//a也是一个数表 
int res[10000];			//记录路径 

void DP()
{
	dp[1] = 1;
	dp[2] = 1;
	for(int i = 3;i <= n;i++)
	{
		//这个状态转移还是有点考验的! 
		if(i % 2 != 0)
			dp[i] = dp[i - 1] + 1;
		else
			dp[i] = dp[i / 2];
	}
	//print dp
	cout << "dp:" << endl;
	for(int i = 1;i <= n;i++)
	{
		cout << dp[i] << " ";
	}
	cout << endl;
}

void F()
{
	a[0] = 0;			//唯一的边界
	for(int i = 1;i <= n;i++)
	{
		//有了前面推二进制递推式的经验,这个很快就能推出来了 
		a[i] = a[i / 10] + i % 10;
	} 
	//print a
	cout << "a:" << endl;
	for(int i = 1;i <= n;i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
}

int main()
{
	cin >> n;
	F();				//预处理a数组,处理各个数各位数的和 
	DP();				//预处理填充dp数组
	int ans = 0;
	int l = 1;
	for(int i = 1;i <= n;i++)
	{
		if(a[i] == dp[i])
		{
			ans++;
			res[l++] = i;		//可以记录下结果 
		}
	} 
	cout << "幸运数有:" << ans  << " 个" << endl;
	//打印结果 
	cout << "分别是:";
	for(int i = 1;i < l;i++)
	{
		if(i != l - 1)
			cout << res[i] << ", ";
		else
			cout << res[i] << endl;
	}
	return 0;
}

运行结果:
DP练习题:求解幸运数问题(递归思想,动规解决)_第2张图片
动规写法使得整个算法时间复杂度O(n),效率相比于递归大大提升

你可能感兴趣的:(DP,递归,数据结构与算法,算法)