CCF 201312-4 有趣的数 动态规划

CCF 有趣的数201412-4

c++实现

题目链接

问题描述

我们把一个数称为有趣的,当且仅当:
  1. 它的数字只包含0, 1, 2, 3,且这四个数字都出现过至少一次。
  2. 所有的0都出现在所有的1之前,而所有的2都出现在所有的3之前。
  3. 最高位数字不为0。
  因此,符合我们定义的最小的有趣的数是2013。除此以外,4位的有趣的数还有两个:2031和2301。
  计算恰好有n位的有趣的数的个数


第一次遇到这个题,我是蒙的,完全没有思路.看了网上的题解再结合一些网络上的资料,发现这道题是典型的动态规划问题.

下面我尝试用通俗易懂的方式解释一下自己的思路

动态规划的特点是:一个大问题可以分解成若干个小问题,然后通过综合多个小问题的最优解得到大问题的的最优解.

这道题目问的是n位有趣的数的个数,那么想得到最终的结果,我们可以怎么做?

借助动态规划思路,n位有趣的数可通过n-1位有趣的数来推断,怎么推断呢?要分情况讨论.题目要求四个数字至少出现一次,n位有趣的数可以通过两种情况来组合:

第一种是前n-1位包含了四个数字,那么第n位只能是0,1.

第二种是前n-1位只包含三个数字.只能是(0,1,2)或(0,2,3).相应的第n位就是3或1.这是为什么呢?题设要求四个数字都出现过至少一次,因此(0,1,3)和(1,2,3)是不合题意的,比如,前面有了0,1,3那么最后一位只能是2,这样导致3出现在2前面.

这时候问题就变成了求n-1位包含(0,1,2)或(0,2,3)有趣的数(暂时忽略四个数字都出现过至少一次 )的个数.用上面同样的思路.比如,n-1位包含(0,2,3)的有趣的数,可通过两种情况组合:

第一种是前n-2位包含了(0,2,3)的有趣的数,第n-1位可以是0,2

第二种是前n-2位包含了(0,2)或(2,3)的有趣的数,相应的第n-1位可以是3和0;

同理继续求n-2位包含了(0,2)或(2,3)的有趣的数的个数,按照这个思路进行下去直到只有1位有趣的数,经过分析只能是2.
分析如下:0不能出现在首位,1不能出现在0前,3也不能出现在2前,所以只能是2.
也就是说第一位只能是2,按照上面的思路倒推即可


先设一个数组num[n][6]表示长度为n时,有趣的数在不同数字组合情况下的个数.6表示有6种情况

  1. 只有2 用数组num[n][0]表示
  2. 有2,0 用数组num[n][1]表示
  3. 有2,3 用数组num[n][2]表示
  4. 有2,0,1 用数组num[n][3]表示
  5. 有2,0,3 用数组num[n][4]表示
  6. 有2,0,1,3 用数组num[n][5]表示
2
2,0
2,3
2,0,1
2,0,3
2,0,1,3
树状表示

// 各状态的递归式
int j = i - 1;
// 只有2 : 长度为i的有趣的数的个数等于长度为i-1的有趣的数的个数
num[i][0] = num[j][0]; 
// 有 2,0 : 长度为i的有趣的数的个数等于长度为i-1的只有2的有趣的数的个数加上有2,0的个数的2倍
// 乘2是因为前面i-1为包含了0,2第n位可以是0,2两种情况所以乘2
num[i][1] = num[j][0] + num[j][1] * 2; 
// 有2,3 : 等于长度为i-1的只有2和有2,3的有趣的数的个数和
// 没有乘2是因为前n-1位是2,3,第n位只能是3
num[i][2] = num[j][0] + num[j][2];
// 有2,0,1 :前n-1位有2,0,1的个数的两倍(第n位可以是2,1)和前n-1位有2,0的个数的一倍
num[i][3] = num[j][3] * 2 + num[j][1];
// 有2,0,3 : 前n-1位有2,0,3的个数的两倍(第n位可以是0,3)和前n-1位有2,0或2,3的个数的一倍
num[i][4] = num[j][4] * 2 + num[j][1] + num[j][2];
// 有2,0,1,3 : 前n-1位有2,0,1,3的个数的两倍(第n位可以是0,2)和前n-1位有2,0,1或2,0,3的个数的一倍
num[i][5] = num[j][5] * 2 + num[j][3] + num[j][4];

程序实现

int main() {
	int n;
	scanf("%d",&n);
	long long mod = 1000000007;
	long long num[n][6];
	
	memset(num,0,sizeof(num));
	num[0][0] = 1; 
	for(int i = 1; i < n; i++) {
		int j = i - 1;
		num[i][0] = num[j][0] % mod;
		num[i][1] = (num[j][0] + num[j][1] * 2) % mod;
		num[i][2] = (num[j][2] + num[j][0]) % mod;
		num[i][3] = (num[j][3] * 2 + num[j][1]) % mod;
		num[i][4] = (num[j][4] * 2 + num[j][1] + num[j][2]) % mod;
		num[i][5] = (num[j][5] * 2 + num[j][3] + num[j][4]) % mod;
	}
	
	cout<<num[n-1][5]<<endl;
	
    return 0;
}

提交结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值