(算法)ccf201312-4有趣的数,动态规划推导过程,c++参考例程

问题描述

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

  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

实现算法

这个算法题显然要用到动态规划的方法解决,关键是找出递推式
思路可以由以下方向入手理解:


将n个数字组成的有趣数的个数记为g(n)
需要明确:任何一个n位有趣数,都可以看做是n-1位数,再在其后面追加0-3其中一个数而组成的
在这个组合过程中,有以下两种情况:

  • 情况1:n-1个数组成的是有趣数,增加一个后还是有趣数
  • 情况2:n-1个数组成的不是有趣数,增加一个后成了有趣数

对于情况1,前n-1位数是由0,1,2,3组成的有趣数,末尾追加1或3(因为要满足有趣数的定义,最后一位不可能是0或2)

对于情况2,又分为几种情况:

  • 情况2.1:前n-1位数由0,1,2组成的类有趣数,末尾追加3
  • 情况2.2:前n-1位数由0,2,3组成的类有趣数,末尾追加1
    (这里的类有趣数指0-3没有全出现且"有1必有0,有3必有2"的数,简单说就是在末尾加上1或3能够构成有趣数的数)

因此,可以得出结论,g(n)和g(n-1)的关系不只与n有关,还与前n-1位数的状态有关,用一维参数变量就很难表示其递推关系了,所以需用二维参数,我们再记f(n,s)为n位状态s下的"类有趣数"的个数。而当状态s为0-3都至少出现一次时,也就是四个数都出现过的"类有趣数",就是我们所求的有趣数。


明确了以上的推导思路过程,再来看下面推导递推式的过程:
上面得出,我们要用f(n,s)为n位状态s下的"类有趣数"的个数
现列出所有类有趣数的形式(状态):

状态0:由2组成
状态1:由2,0组成
状态2:由2,3组成
状态3:由0,1,2组成
状态4:由0,2,3组成
状态5:由0,1,2,3组成(此时为有趣数)

即我们所要求的是f(n,5)的值

显然

f(n,5) = 2 × f(n-1,5) + f(n-1,3) + f(n-1,4)

(状态5时在末尾可以追加1、3,状态3时可以追加3,状态4时可以追加1,所有分别乘2、1、1)
类推分别得出f(n,4)f(n,3)f(n,2)f(n,1)

f(n,4) = 2 × f(n-1,4) + f(n-1,1) + f(n-1,2)

f(n,3) = 2 × f(n-1,3) + f(n-1,1)

f(n,2) = f(n-1,2) + f(n-1,0)

f(n,1) = 2 × f(n-1,1) + f(n-1,0)

f(n,0) = 1

有了递推式,程序实现就简单多了,这里写了一个自底向上的动态规划写法供参考。

参考程序

#include
#define PRIME 1000000007
using namespace std;
_int64 f[1000][6];  //_int64支持vc6.0
void main(){
	int n;
	cin >> n;
	f[1][0] = 1;
	for(int i = 2;i <= n;i++){
		f[i][0] = 1;
		f[i][1] = (2 * f[i-1][1] + f[i-1][0]) % PRIME;
		f[i][2] = (f[i-1][2] + f[i-1][0]) % PRIME;
		f[i][3] = (2 * f[i-1][3] + f[i-1][1]) % PRIME;
		f[i][4] = (2 * f[i-1][4] + f[i-1][1] + f[i-1][2]) % PRIME;
		f[i][5] = (2 * f[i-1][5] + f[i-1][3] + f[i-1][4]) % PRIME;
	}
	printf("%I64d\n",f[n][5]);  
}

你可能感兴趣的:(数据结构与基本算法)