CCF模拟题4-有趣的数

本文介绍了一种算法,用于计算特定条件下有趣数字的数量。有趣数字的特点是仅包含0、1、2、3四个数字,且每个数字至少出现一次,0位于所有1之前,2位于所有3之前,最高位不为0。通过动态规划的方法,定义状态转移方程来求解给定长度下有趣数字的个数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CCF模拟题4-有趣的数

问题描述

我们把一个数称为有趣的,当且仅当:

  1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
  2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前。
  3. 最高位数字不为0。
    因此,符合我们定义的最小的有趣的数是2013。除此以外,4位的有趣的数还有两个:2031和2301。
    请计算恰好有n位的有趣的数的个数。由于答案可能非常大,只需要输出答案除以1000000007的余数。
    输入格式

输入只有一行,包括恰好一个正整数n (4 ≤ n ≤ 1000)。
输出格式:输出只有一行,包括恰好n 位的整数中有趣的数的个数除以1000000007的余数。

样例输入
4
样例输出
3

一、思路

这里约束了:0与1、2与3的关系

考虑第一个数:这个数只能是2
考虑第二个数:0或者3或者2
这里可以得出一个结论,如果只有两个的话,其中一个必然是2,且另一个不可能是1
第三个数的取值范围与第二个数有关系

那么,如此类推下去,假设长度为n,我们需要取第i个数:
这里你可以将0视为出现1的前提条件,反过来,出现1之后,就不能出现0了。
将2视为出现3的前提条件,反过来,出现3之后,就不能出现2了。
第i个数的取值肯定是和之前有关系的,考虑之前出现过的状态:

(1)只出现了2

这种情况下,我们可以取的数有:0,2,3

(2)出现了2,3

这种情况下,我们可以取的数有:0,3
因为出现了3,3不能在2的前面,所以取不了2了

(3)出现了0,2

这种情况下,我们可以取的数有:0,1,2,3

(4)出现了0,1,2

这种情况下,我们可以取的数有:1,2,3

(5)出现了0,2,3

这种情况下,我们可以取的数有:0,1,3
因为0出现了,所以可以取1了

(6)出现了0,1,2,3四个数字

这种情况下,我们可以取的数有:1,3
因为出现了3,3不能在2的前面,所以取不了2了
因为出现了1,1不能在0的前面,所以取不了0了

注意:有的情况是不可能出现的,比如:(1,2)、(1,3)这种

这里,我们给出如下两个要求:
(1)0只能出现在1前面
(2)2只能出现在3前面
现在我们假设f(i,j)f(i,j)f(i,j)表示:
给定数的长度为i,且之前的状态是j时,所能生成的满足上述两个条件的个数
那么这个f(i,j)f(i,j)f(i,j)与我们要求解的问题有什么关系呢?
没错,我们有趣的数需要满足三个条件,除了上述两个外,第三个条件:
(3)0,1,2,3都要出现
这正好是状态6的情况
因此f(i,6)f(i,6)f(i,6)就是长度为iii的有趣的数的个数

现在来看看f(i,6)f(i,6)f(i,6)是怎么来的,生成f(i,6)f(i,6)f(i,6)有几种办法呢?先考虑一步走
无非是:

  • 长度为i−1i-1i1的时候,到达状态6,即:f(i−1,6)f(i-1,6)f(i1,6),最后一个数从1和3中选一个
  • 长度为i−1i-1i1的时候,到达状态5,即:f(i−1,5)f(i-1,5)f(i1,5),最后一个数只能是1,这样才能满足条件(3)
  • 长度为i−1i-1i1的时候,到达状态4,即:f(i−1,4)f(i-1,4)f(i1,4),最后一个数只能是3,这样才能满足条件(3)
    除此以外还有别的方法吗?考虑两步走:
  • 长度为i−2i-2i2的时候,到达状态6,这种情况下与一步走的情况(1)相同,重复了
  • 长度为i−2i-2i2的时候,到达状态5,这种情况下,决定下一个数时,会转换为一步走的三种情况中的一个

    其余情况不再累述,事实上两步走终究是要变到一步走的

所以我们推出了状态转移方程:
f(i,6)=2∗f(i−1,6)+f(i−1,5)+f(i−1,4)f(i,6)=2*f(i-1,6)+f(i-1,5)+f(i-1,4)f(i,6)=2f(i1,6)+f(i1,5)+f(i1,4)

这个时候,你可能会说,f(i−1,6)、f(i−1,5)、f(i−1,4)f(i-1,6)、f(i-1,5)、f(i-1,4)f(i1,6)f(i1,5)f(i1,4)也不好算啊

其实,通过上面的公式,原问题的规模从iii下降到了i−1i-1i1,并且复杂度也有下降
f(i−1,6)f(i-1,6)f(i1,6)的计算公式与f(i,6)f(i,6)f(i,6)一样

那么就来看看f(i−1,5)f(i-1,5)f(i1,5)该怎么算了,还是考虑一步走:

  • 长度为i−2i-2i2的时候,到达状态5,即:f(i−2,5)f(i-2,5)f(i2,5),最后一个数:0或者3
  • 长度为i−2i-2i2的时候,到达状态2,即:f(i−2,3)f(i-2,3)f(i2,3),最后一个数只能是3,这样才能到状态5
  • 长度为i−2i-2i2的时候,到达状态3,即:f(i−2,2)f(i-2,2)f(i2,2),最后一个数只能是0,这样才能到状态5

于是:
f(i−1,5)=2∗f(i−2,5)+f(i−2,2)+f(i−2,3)f(i-1,5)=2*f(i-2,5)+f(i-2,2)+f(i-2,3)f(i1,5)=2f(i2,5)+f(i2,2)+f(i2,3)
同样对f(i−1,4)f(i-1,4)f(i1,4)分析之后,有:
f(i−1,4)=2∗f(i−2,4)+f(i−2,3)f(i-1,4)=2*f(i-2,4)+f(i-2,3)f(i1,4)=2f(i2,4)+f(i2,3)

好了,剩下的拆分,也是一样的道理:
f(i−2,2)=f(i−3,2)+f(i−3,1)f(i-2,2) = f(i-3,2)+f(i-3,1)f(i2,2)=f(i3,2)+f(i3,1)
f(i−2,3)=f(i−3,3)+f(i−3,1)f(i-2,3) = f(i-3,3)+f(i-3,1)f(i2,3)=f(i3,3)+f(i3,1)
初始条件:
f(n,1)=1f(n,1)=1f(n,1)=1

C++代码:

class Solution {
public:
	int findInterestNums(int n) {
		vector<vector<long long> > dp(n + 1, vector<long long>(6));
		// 初始化
		for (int i = 0; i < 6; i++)
			dp[0][i] = 0;
		// 计算
		for (int i = 1; i <= n; i++) {
			long long j = i - 1;
			dp[i][0] = 1;
			dp[i][1] = dp[j][1] + dp[j][0];
			dp[i][2] = dp[j][2] + dp[j][0];
			dp[i][3] = 2 * dp[j][3] + dp[j][2];
			dp[i][4] = 2 * dp[j][4] + dp[j][2] + dp[j][1];
			dp[i][5] = 2 * dp[j][5] + dp[j][4] + dp[j][3];
		}		
		return dp[n][5];
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值