第一届CCF考试题解(4)

来源:互联网 发布:网络研修的收获 编辑:程序博客网 时间:2024/06/05 20:10

有趣的数(2013年12月)

原创文章,转载请注明出处


问题描述
  我们把一个数称为有趣的,当且仅当:
  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部分,要满足所有的0在1的前面。第二个是2和3部分,要满足所有的2在3的前面。假设0和1的序列已经定好了长度为x,2和3的序列也已经定好了长度为y,将两者合起来就是一个长度为(x+y)的满足题目要求的序列。如果把01和23两个序列想象成男女两队,很容易就能联想起这是一个经典的男女组合问题,从(x+y)个位置中,挑出x个位置放男生,剩下的Y个位置放女生,那样所有的排列数是。

sum=(x+yx)

再考虑长度为x的01串所有可能性,将01串看成是一个二进制串,0表示当前位为0,1表示当前位为1。要满足0在1前的所有串如下:(以x = 5举例)
01111
00111
00011
00001
很明显只有4个,而且规律是从第1位开始,第i个新的串是第i位的1变成0.那么对于长度为x的01串,满足题目要求的个数是x - 1。
再考虑长度为y的23串所有可能性,将23串看成是一个二进制串,0表示当前位为2,1表示当前位为3。要满足2在3前的所有串如下:(以y = 5举例)
11111
01111
00111
00011
00001
在这里与01串不一样的地方出现了,因为根据题目要求,很容易推出该数的第1位一定是2,所以后面的23串可以从3开头。满足要求的个数是y 。

那么对于一个长度为n的有趣的数,我们要考虑的部分只有后面n-1位,第1位一定是2。后面的n-1位可能有长度为2的01串 + 长度为n - 3的23串,或长度为3的01串 + 长度为n - 4的23串,或长度为4的01串 + 长度为n - 5的23串……或长度为n-2的01串+长度为1的23串构成。
则可以推出公式为:

f(n)=(21)×(n12)×(n3)+(31)×(n13)×(n4)+(41)×(n14)×(n5)+...+(n21)×(n1n2)×(1)

f(n)=i=2n2(i1)×(n1i)×(ni1)

公式里面全是加法和乘法对取模没有影响,难点在于组合数求解,(1000500)是个十分庞大的数,直接求解肯定不行,要是每个数都算好几遍肯定也会超时。这时候就得用DP的思想来考虑这个问题,每个子结构的数值都存储下来,用空间换取时间。因为最终结果要取模,所以只能考虑加法和乘法,最后锁定了这个公式。
这里写图片描述
用二维矩阵[i][j]存储(ij)即可。


//// Created by cxj on 17-3-16.//#include "stdio.h"#define MOD 1000000007int main(){    long long arry[600];    long long sum = 0;    long long C_number[1005][1005] = {0};    int n;    scanf("%d",&n);    arry[0] = 1;/*c(i,1)赋值1 和 c(i,i)赋值1*/        for(int i = 1; i <= n; i++) {            C_number[i][1] = i;            C_number[i][i] = 1;        }/*计算整个组合表*/        for(int i = 2; i < n; i++)        {            for(int j = 2; j < i; j++)            {                if(C_number[i][i - j])C_number[i][j] = C_number[i][i - j];                else                {                    for(int k = j - 1; k < i; k++)                    {                        C_number[i][j] += C_number[k][j - 1];                        C_number[i][j] = C_number[i][j] % MOD;                    }                }            }        }/*组后的求和公式*/    sum = 0;    for(int i = 2; i <= n - 2; i++)    {        sum += C_number[n - 1][i] * (i - 1) * (n - 1 - i);        sum = sum % MOD;    }    printf("%lld",sum);    return 0;}
0 0