(CCF 201312-4)有趣的数 [DP] 最详细解答

问题描述
  我们把一个数称为有趣的,当且仅当:
  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组成的所有满足一定要求的排列数的 数目。

我的第一想法就是dfs+剪枝。后来分析了一下就知道超时了。因为结果很大,我们再怎么剪枝,也需要将所有的结果找出来一遍,而结果最大值会大于所给的求模的值,所以一定超时。

所以没法往找出所有的结果的方向思考了。那就只有通过推导出他们之间的关系和推导公式来解决问题了。所以就只能向dp思考了。

对于一个给定的合格的数,如2013这个四位数,要是再往后扩展一位,会是什么样的结果呢?
并且我们可以很快分析出,第一个数只能是2

我们将0,1,2,3所有的满足条件2,3的情况进行归纳总结:
(1):所有的数都出现过一次:

 5--全部用了

(2):有一个数没有用:只能是1或3没有用,因为若0没有用,那说明1也一定还没有用,不然后面无法出现0了,对于2同理可得,因为若2没有用,那说明3也一定还没有用

 4--用了0,2,3,剩1
 3--用了0,1,2,剩3

(3)**有两个数没有用:**0,1或者1,3没有用,同理由于条件2,使得其他情况不存在满足的。

 2--用了2,3,剩0,1
 1--用了0,2,剩1,3

(4)有三个数没有用:只能是0,1,3

 0--用了2,剩0,1,3

我们用dp[i][0] ~ dp[i][5]依次表示为当长度为i时,各种情况的组合的数目。
所以递推公式为:

dp[i][0] = 1;
当只用了2时,只用一种情况2..222

dp[i][1] = (dp[i-1][0] + dp[i-1][1]*2) % mod;
在i-1位的条件下,dp[i-1][0]只用了2,所以在第i位为:0
在i-1位的条件下,dp[i-1][1]只用了0,2,所以在第i位为:0或2

dp[i][2] = (dp[i-1][0] + dp[i-1][2]) % mod;
在i-1位的条件下,dp[i-1][0]只用了2,所以在第i位为:3
在i-1位的条件下,dp[i-1][2]只用了2,3,所以在第i位为:3(2在3的前面)

dp[i][3] = (dp[i-1][1] + dp[i-1][3]*2) % mod;
在i-1位的条件下,dp[i-1][1]只用了0,2,所以在第i位为:1
在i-1位的条件下,dp[i-1][3]只用了0,1,2,所以在第i位为:1或2

dp[i][4] = (dp[i-1][1] + dp[i-1][2] + dp[i-1][4]*2) % mod;
在i-1位的条件下,dp[i-1][1]只用了0,2,所以在第i位为:3
在i-1位的条件下,dp[i-1][2]只用了2,3,所以在第i位为:0
在i-1位的条件下,dp[i-1][4]只用了0,2,3,所以在第i位为:0或3

dp[i][5] = (dp[i-1][3] + dp[i-1][4] + dp[i-1][5]*2) % mod;
在i-1位的条件下,dp[i-1][3]只用了0,1,2,所以在第i位为:3
在i-1位的条件下,dp[i-1][4]只用了0,2,3,所以在第i位为:1
在i-1位的条件下,dp[i-1][5]全用了,所以在第i位为:1或3

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;

const int maxn = 1010;
const long long mod = 1000000007;

long long dp[maxn][10];

void init()
{
    for(int i=0;i<6;i++) dp[0][i] = 0;
    for(int i=1;i<maxn;i++)
    {
        dp[i][0] = 1;
        dp[i][1] = (dp[i-1][0] + dp[i-1][1]*2) % mod;
        dp[i][2] = (dp[i-1][0] + dp[i-1][2]) % mod;
        dp[i][3] = (dp[i-1][1] + dp[i-1][3]*2) % mod;
        dp[i][4] = (dp[i-1][1] + dp[i-1][2] + dp[i-1][4]*2) % mod;
        dp[i][5] = (dp[i-1][3] + dp[i-1][4] + dp[i-1][5]*2) % mod;
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    int n;
    init();
    scanf("%d",&n);
    printf("%d\n",dp[n][5]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值