有趣的数 算法的题解(数位DP问题)

有趣的数 算法的题解(数位DP问题)

1. 想了解数位DP问题的可以先参考该文档

2. 问题概述

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

  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

3. 问题求解

首先定义f[i]表示为i位数满足条件的组合数。
f[i,2]表示头为2的后面i位共有满足条件的组合数。
根据图可以看出20(是指第一位为2第二位为0,当然倒过来也是成立的)后面可能的组合和200后面可以选择的组合是相同的,这点必须要先明白。这样就有如下等式成立:

f[i, 200] = f[i, 20]
f[i, 220] = f[i, 20]

图

下面开始进行迭代公式:

因为第一个数字必须为2否则会违反规则,所以有如下公式:
f[i] = f[i-1, 2]/意思为i位数的满足规则的组合数与2为头剩下的i-1位满足规则的组合数相等。
另外,f[i]即为我们所求的在i位数下的组合数。
第一位为2,第2位上只能是0、2、3之中的一个,所以可以有如下公式:
f[i-1, 2] = f[i-2, 20] + f[i-2, 22] + f[i-2, 23]
根据前面公式,有f[i-2, 22] = f[i-2, 2]
同理有如下公式:
f[i-2, 20] = f[i-3, 200] + f[i-3, 201] + f[i-3, 202] + f[i-3, 203] = f[i-3, 20] + f[i-3, 201] + f[i-3, 20] + f[i-3, 203]
f[i-2, 22](=f[i-2, 2]) = f[i-3, 220] + f[i-3, 222] + f[i-3, 223] = f[i-3, 20] + f[i-3, 2] + f[i-3, 23]
f[i-2, 23] = f[i-3, 230] + f[i-3, 233] = f[i-3, 230] + f[i-3, 23]
f[i-3, 230] = f[i-4, 2300] + f[i-4, 2301] + f[i-4, 2303] = f[i-4, 230] + f[i-4, 2301] + f[i-4, 230]
f[i-3, 201] = f[i-4, 2011] + f[i-4, 2012] + f[i-4, 2013] = f[i-4, 201] + f[i-4, 201] + f[i-4, 2013]

公式里面的{2}、{20}、{23}、{201}、{203}、{2031}称为数码。

另外因为2013后面的数字只能为1或者3,组合数为2,即

f[1, 2013] = 2;可以推导出f[i, 2013] = 2^(i);

下面就是数列的计算,高中已经学过了,计算的结果为

f[i] = f[i-1, 2] = (i-1)(i-4)2^(i-3)+i-1

从而得到的结果为N依次为4到15,结果为

Columns 1 through 7

       3          20          85         294         903        2568        6921

Columns 8 through 12

   17930       45067      110604      266253      630798

用笔算大概就是这个思路。
通过程序实现,就是针对上面的迭代公式进行迭代。
java程序代码如下:

package test;

import java.util.Arrays;

public class test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int N = 15;
        long[] count = new long[8];
        count[6] = 0;
        count[7] = 1;
        long mod = 1000000007;
        for (int i = 2; i <= N; ++i) {
            long[] newCount = new long[8];
            newCount[0] = (count[0] * 2 + count[1] + count[3]) % mod;
            newCount[1] = (count[1] * 2 + count[2] + count[5]) % mod;
            newCount[2] = (count[2] + count[6]) % mod;
            newCount[3] = (count[3] * 2 + count[4] + count[5]) % mod;
            newCount[4] = (count[4] + count[7]) % mod;
            newCount[5] = (count[5] * 2 + count[6] + count[7]) % mod;
            newCount[6] = 0;
            newCount[7] = 1;
            count = newCount;
            System.out.println(count[0]);
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值