CCF模拟题4-有趣的数
问题描述
我们把一个数称为有趣的,当且仅当:
- 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
- 所有的0都出现在所有的1之前,而所有的2都出现在所有的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)就是长度为
i
i
i的有趣的数的个数
现在来看看
f
(
i
,
6
)
f(i,6)
f(i,6)是怎么来的,生成
f
(
i
,
6
)
f(i,6)
f(i,6)有几种办法呢?先考虑一步走
无非是:
- 长度为 i − 1 i-1 i−1的时候,到达状态6,即: f ( i − 1 , 6 ) f(i-1,6) f(i−1,6),最后一个数从1和3中选一个
- 长度为 i − 1 i-1 i−1的时候,到达状态5,即: f ( i − 1 , 5 ) f(i-1,5) f(i−1,5),最后一个数只能是1,这样才能满足条件(3)
- 长度为
i
−
1
i-1
i−1的时候,到达状态4,即:
f
(
i
−
1
,
4
)
f(i-1,4)
f(i−1,4),最后一个数只能是3,这样才能满足条件(3)
除此以外还有别的方法吗?考虑两步走: - 长度为 i − 2 i-2 i−2的时候,到达状态6,这种情况下与一步走的情况(1)相同,重复了
- 长度为
i
−
2
i-2
i−2的时候,到达状态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)=2∗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(i-1,6)、f(i-1,5)、f(i-1,4) f(i−1,6)、f(i−1,5)、f(i−1,4)也不好算啊
其实,通过上面的公式,原问题的规模从
i
i
i下降到了
i
−
1
i-1
i−1,并且复杂度也有下降
f
(
i
−
1
,
6
)
f(i-1,6)
f(i−1,6)的计算公式与
f
(
i
,
6
)
f(i,6)
f(i,6)一样
那么就来看看 f ( i − 1 , 5 ) f(i-1,5) f(i−1,5)该怎么算了,还是考虑一步走:
- 长度为 i − 2 i-2 i−2的时候,到达状态5,即: f ( i − 2 , 5 ) f(i-2,5) f(i−2,5),最后一个数:0或者3
- 长度为 i − 2 i-2 i−2的时候,到达状态2,即: f ( i − 2 , 3 ) f(i-2,3) f(i−2,3),最后一个数只能是3,这样才能到状态5
- 长度为 i − 2 i-2 i−2的时候,到达状态3,即: f ( i − 2 , 2 ) f(i-2,2) f(i−2,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(i−1,5)=2∗f(i−2,5)+f(i−2,2)+f(i−2,3)
同样对
f
(
i
−
1
,
4
)
f(i-1,4)
f(i−1,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(i−1,4)=2∗f(i−2,4)+f(i−2,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(i−2,2)=f(i−3,2)+f(i−3,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(i−2,3)=f(i−3,3)+f(i−3,1)
初始条件:
f
(
n
,
1
)
=
1
f(n,1)=1
f(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];
}
};