数列 (卡特兰数对非质数取模)[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);
}
posted @ 2022-05-06 17:36  Aiza  阅读(111)  评论(0编辑  收藏  举报