CCF201312-4 有趣的数 不会DP只会枚举

来源:互联网 发布:ubuntu下安装jdk9 编辑:程序博客网 时间:2024/03/28 23:09

========================================= 原题 ============================================

问题描述
  我们把一个数称为有趣的,当且仅当:
  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

========================================= 思路 ============================================

听闻网上大牛动不动就数位DP...宝宝做不到啊 =、=

看到这个题想了半天递推公式,感觉不是很麻烦但就是不想推...然后就有了下面这种乱七八糟的解法:

首先看到题发现了两个问题:

1、0/1和2/3之间没有特殊联系

2、数字首位必须是2

先解释下首位2的问题:如题0/2必须在1/3前面,0/1/2/3必须出现至少一次,所以首位只能是0/2(否则总有一个无法出现),但0不能做首位,所以首位必定是2。

然后就是最关键的一点——首个1首个3的位置问题。

关于一个n位有趣数的可能情况如果直接写可能非常复杂,即便按照递推式也很麻烦,但只关注首个1/3就简单多了:1出现的比3早、3出现的比1早。

假设首个1出现在第 i 位,首个3出现在第 j 位。

① 1出现的比3早(i < j)


由假设可知,在0~i 位之间没有1/3只有0/2,在 i ~ j 之间只有1/2,在 j ~ n 之间只有1/3,出去固定位置(0、i、j)外每个位置都有两种可能,共2^(n-3)种可能。其中存在不符合要求的可能性——全是2没有0——i 之前没有0,i之后不影响。所以减去错误可能2^(n-i-1),所得结果即为当前i、j情况下结果总数。

② 3出现的比1早(i > j)

情况其实和前一种类似,只是考虑错误情况时候的计算变成了2^(n-i)种可能了(3在前面)。

知道了每种情况下的错误计算,还需要注意一下总数的问题:这里总结果数取的是【1/3可能数 × 0/2可能数 - 错误数】,其中1/3可能因为3的位置出现区别(3可以出现在第2位但1不可以,否则0无法出现)。

下面是AC代码(附带两个防溢出的函数):

#define MOD 1000000007#include<iostream>using namespace std;int getnum(int n){int k = 1;for (int i = 1; i <= n; i++){k *= 2;k %= MOD;}return k;}int mul(int a, int b){int k = 0;for (int i = 0; i < b; i++){k += a;k %= MOD;}return k;}int main(){int n;cin >> n;long long n13 = ((n - 2)*(n - 3) + n - 2) % MOD;long long n02 = getnum(n-3);long long ans = (n13*n02) % MOD;// 这里直接把3出现的可能乘进去了// 因为2必定出现所以不用单独考虑3的错误// 首个1出现在首个3之前for (int i = 3; i < n; i++){if (n - i - 1 >= 0){int wr = mul(getnum(n - i - 1), n - i);ans -= wr;// 分步取模可能出现计算后负数while (ans <= 0){ans += MOD;}}}// 首个3出现在首个1之前for (int i = 3; i <= n; i++){if (n - i >= 0){int wr = mul(getnum(n - i), i - 2);ans -= wr;while (ans <= 0){ans += MOD;}}}cout << ans%MOD << endl;return 0;}



原创粉丝点击