BZOJ 1485 - [HNOI2009]有趣的数列
Published on 2017-01-17描述
我们称一个长度为 的数列是有趣的,当且仅当该数列满足以下三个条件:
- 它是从 到 共 个整数的一个排列 ;
- 所有的奇数项满足 ,所有的偶数项满足 ;
- 任意相邻的两项 与 满足奇数项小于偶数项,即:。
现在的任务是:对于给定的 ,请求出有多少个不同的长度为 的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 的值。
分析
我们只需要选出 个数作为奇数项,这个数列便可唯一确定,我们考虑怎么选择奇数项才能构成“有趣”的数列。因为要满足 ,所以很容易发现一个事实:「前 个数中,未选择的数的个数不能超过被选择的数的个数」。原因是由于未选择的数是按照顺序依次填入偶数项的,如果未选择的项超过了被选择的项,那么多出来的偶数项一定会与比 大的奇数项相邻,这就不满足第三个约束条件了。否则可用归纳法证得所有项都满足第三个约束条件,这就是构成有趣的数列的充要条件。
所以我们将问题转化为了这样一个经典模型:「求一个 位、含 个 、 个 的二进制数,满足从左往右扫描到任意一位时,经过的 数不多于 数」,其答案就是卡特兰数的第 项 ,考虑卡特兰数的通项公式:
由于要对 取模, 不是质数,所以不能采用预处理逆元的办法。由于只需要求 2 次组合数,将组合数拆为阶乘表达式,依次考虑每个质因子在 中出现了多少次,最后用快速幂乘起来就行了,这样做的复杂度是 的。
当然也可以将式子转化为这样:
可少处理几次阶乘,常数小一点。
代码
// Created by Sengxian on 2017/01/17. // Copyright (c) 2017年 Sengxian. All rights reserved. // BZOJ 1485 卡特兰数 #include <bits/stdc++.h> using namespace std; typedef long long ll; inline int readInt() { static int n, ch; n = 0, ch = getchar(); while (!isdigit(ch)) ch = getchar(); while (isdigit(ch)) n = n * 10 + ch - '0', ch = getchar(); return n; } const int MAX_N = 2000000 + 3; int n, MOD, fact[MAX_N]; int primes[MAX_N], cnt = 0; bool isPrime[MAX_N]; void prepare() { fill(isPrime, isPrime + MAX_N, 1); for (int i = 2; i < MAX_N; ++i) { if (isPrime[i]) primes[cnt++] = i; for (int j = 0; j < cnt && i * primes[j] < MAX_N; ++j) { int t = i * primes[j]; isPrime[t] = false; if (i % primes[j] == 0) break; } } } inline int modPow(ll a, int b) { ll res = 1; while (b) { if (b & 1) (res *= a) %= MOD; (a *= a) %= MOD; b >>= 1; } return res; } void cal(int n, int sign) { for (int i = 0; i < cnt && primes[i] <= n; ++i) { ll t = primes[i]; while (t <= n) { fact[i] += sign * n / t; t *= primes[i]; } } } int C(int n, int k) { memset(fact, 0, sizeof fact); cal(n, 1), cal(n - k, -1), cal(k, -1); ll res = 1; for (int i = 0; i < cnt; ++i) (res *= modPow(primes[i], fact[i])) %= MOD; return res; } int main() { prepare(); n = readInt(), MOD = readInt(); printf("%d\n", (C(n * 2, n) - C(n * 2, n - 1) + MOD) % MOD); return 0; }