CCF CSP 有趣的数

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

===================================================================================================================

这道题分析后就可以用高中数学解了,研究生期间也会学组合数学

假设“有趣的数”分别包含 a, b, c, d 个0, 1, 2, 3.

先考虑 (0, 1) 的排列情况,由于0在1前,所以形式是0 0 .. 1 ...1 1。0不能在最高为,故最高位需要使用一个2(即最高位必须是2),剩下 (c + d - 1) 个 (2, 3).

由于 2 必须在3前面。所以把(c + d - 1)个 2 | 3插入 0 1串中,就是高中学的“插空法”。(a + b + 1)个数产生(a + b + 1 + 1)个空。最高位已确定为2,其他2(若有),插在最高位左右一样,所以就是把 (c + d - 1)个数插入(a + b + 1)个空中。


C(a + b + 1 + c + d - 1 - 1, c + d - 1) * (c + d - 1) * ( a + b - 1).      

(c + d -1)为 (c + d)个(2, 3)中2或3的个数,(a + b - 1)为 (0, 1)中0或1的个数。

计算这个式子,得计算组合数,我用的快速幂+费马小定理求逆元。也可用 拓展欧几里得。


#include 
typedef long long LL;
const int MOD = 1e9 +7, N = 2017; 
LL fac[N];
void init() {
	fac[0] = fac[1] = 1;
	for (int i = 2; i < N; ++i) {
		fac[i] = fac[i - 1] * i % MOD;
	}
}
LL pow_mod(LL a, LL n) {
	LL base = a, res = 1;
	while(n) {
		if(n & 1) res = res * base % MOD;
		base = base * base % MOD;
		n >>= 1;
	}
	return res;
}
LL C(LL n, LL m) {
	return fac[n] * pow_mod(fac[n - m], MOD - 2) % MOD * pow_mod(fac[m], MOD - 2) % MOD; 
}
int main()
{
	//ios::sync_with_stdio(false);
	init();
	int n;
	scanf("%d", &n);
	//2 3 最多共有(n-2)个 
	long long sum = 0;
	for (int i23 = 1; i23 <= n - 2; ++i23) {
		sum = (sum + C(n - 1, i23 - 1) * (i23 - 1) % MOD * (n - i23 -1) % MOD) % MOD;
	} 
	printf("%lld\n", sum);

	return 0;
}





你可能感兴趣的:(认证考试,CCF,CSP认证)