数列 (卡特兰数对非质数取模)[2020.5.2]
数列
题目描述
我们称一个长度为 2n 的数列是有趣的,当且仅当该数列满足以下三个条件:
(1)它是从 1 到 2n 共 2n 个整数的一个排列 {$a_i$};
(2)所有的奇数项满足 $a_1 < a_2 < … < a_{2n-1}$,所有的偶数项满足 $a_2 < a_4 < … < a_{2n}$;
(3)任意相邻的两项 $a_{2i - 1}$与$a_{2i}\ \ (1≤i≤n)$满足奇数项小于偶数项,即:$a_{2i}-1\ <\ a2i$。 现在的任务是:对于给定的 n,请求出有多少个不同的长度为 2n 的有趣的数列。因为 最后的答案可能很大,所以只要求输出答案 mod P 的值。输入格式
输入文件只包含用空格隔开的两个整数 n 和 P。
输出格式
仅含一个整数,表示不同的长度为 2n 的有趣的数列个数 mod P 的值。
样例输入
3 6
样例输出
5
数据范围与约定
对于 30%的数据满足 $n≤1000$; 对于另外 30%的数据满足 P 为质数; 对于 100%的数据满足 $n≤10^6$ 且 $P≤10 ^ 9$。
解题思路
把题目中描述的 奇数项 和偶数项看做两个栈
将 1~2n 这些数从小到大压入两个栈
这样就自然满足递增的条件
还有一个要求就是对应的奇数项要小于偶数项,又考虑到数值小的先入栈
所以只需要保证奇数项的栈的深度 <= 偶数项的栈的深度即可
这样就是熟悉的车站调度问题,方案总数即为卡特兰数
可以使用组合数的方法来求 \(C(n) = \frac{C_{2n}^n}{n + 1}\)
这是对于P为质数的情况 可以用Lucas定理快速求解
如果P为任意数 那就只能分解质因数
针对这道题代码给出了 阶乘分解\(\mathrm{O}(n)\) 的做法
code:
Click for the code.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 7;
int n, p;
int tag[maxn], pri[maxn], cnt;
int sum[maxn];
void do_prime() {
tag[1] = 1;
for (int i = 2; i <= maxn; i++) {
if (!tag[i]) {
pri[++cnt] = i;
tag[i] = i;
}
for (int j = 1; j <= cnt && i * pri[j] < maxn; j++) {
tag[i * pri[j]] = pri[j];
if (i % pri[j] == 0) break;
}
}
}
int fast_pow(int x, int ti) {
int res = 1;
while (ti) {
if (ti & 1) res = 1ll * res * x % p;
x = 1ll * x * x % p;
ti >>= 1;
// printf("%d", ti);
}
return res;
}
int main() {
// freopen("sequence.in", "r", stdin);
// freopen("sequence.out", "w", stdout);
scanf("%d %d", &n, &p);
do_prime();//欧拉筛,i的最小质因数存储在tag[i]中
for (int i = 2; i <= n; i++) sum[i] = -1;
for (int i = n + 2; i <= 2 * n; i++) {
sum[i] = 1;
}
for (int i = 2 * n; i >= 2; i--) {
if (tag[i] < i) { //如果i是合数
sum[tag[i]] += sum[i];
sum[i / tag[i]] += sum[i];
}
}
int ans = 1;
for (int i = 2; i <= 2 * n; i++) {
// cout<<"done\n";
if (tag[i] == i) {
// printf("%d %d\n", i, sum[i]);
ans = 1ll * ans * fast_pow(i, sum[i]) % p;
}
}
printf("%d", ans);
}