[Luogu P3200] [BZOJ 1485] [HNOI2009]有趣的数列

22 篇文章 0 订阅
4 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

我们称一个长度为 2 n 2n 2n的数列是有趣的,当且仅当该数列满足以下三个条件:

(1)它是从 1 1 1 2 n 2n 2n 2 n 2n 2n个整数的一个排列 { a i } \{a_i\} {ai}

(2)所有的奇数项满足 a 1 &lt; a 3 &lt; . . . &lt; a 2 n − 1 a_1&lt;a_3&lt;...&lt;a_{2n-1} a1<a3<...<a2n1,所有的偶数项满足 a 2 &lt; a 4 &lt; . . . &lt; a 2 n a_2&lt;a_4&lt;...&lt;a_{2n} a2<a4<...<a2n

(3)任意相邻的两项 a 2 i − 1 a_{2i-1} a2i1 a 2 i ( 1 ≤ i ≤ n ) a_{2i}(1\le i\le n) a2i(1in)满足奇数项小于偶数项,即: a 2 i − 1 &lt; a 2 i a_{2i-1}&lt;a_{2i} a2i1<a2i

现在的任务是:对于给定的 n n n,请求出有多少个不同的长度为 2 n 2n 2n的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 m o d   P mod\ P mod P的值。

输入输出格式

输入格式:

输入文件只包含用空格隔开的两个整数 n n n P P P。输入数据保证,50%的数据满足 n ≤ 1000 n\le 1000 n1000,100%的数据满足 n ≤ 1000000 n\le 1000000 n1000000 P ≤ 1000000000 P\le 1000000000 P1000000000

输出格式:

仅含一个整数,表示不同的长度为 2 n 2n 2n的有趣的数列个数 m o d   P mod\ P mod P的值。

输入输出样例

输入样例#1:
3 10
输出样例#1:
5

解题分析

先画一个图, 大概是这个样子的:

1 → 3 → 5....
↓   ↓   ↓....
2 → 4 → 6....

然后发现可以把第一行的视为左括号, 第二行视为右括号即可。 于是就是一个卡特兰数。

发现模数很大, 但 n n n相对较小, 所以直接对阶乘分解质因数, 最后快速幂。总复杂度 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

代码如下:

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 2005000
#define MP 150050
int n, mod, pcnt;
int pri[MP], up[MP], down1[MP], down2[MP], dat[MX];
bool npr[MX];
IN int fpow(R int base, R int tim)
{
	int ret = 1;
	W (tim)
	{
		if (tim & 1) ret = 1ll * ret * base % mod;
		base = 1ll * base * base % mod, tim >>= 1;
	}
	return ret;
}
void sieve()
{
	R int i, j, tar;
	long long k;
	for (i = 2; i <= 2e6; ++i)
	{
		if (!npr[i]) pri[++pcnt] = i;
		for (j = 1; j <= pcnt; ++j)
		{
			tar = i * pri[j];
			if (tar > 2e6) break;
			npr[tar] = true;
			if (!(i % pri[j])) break;
		}
	}
	for (i = 1; i <= pcnt; ++i)
	for (k = pri[i]; k <= 2e6; k = k * pri[i]) dat[k] = i;
}
IN void solve(R int bd, int *res)
{
	for (R int i = 1; i <= bd; ++i) if (dat[i])
	res[dat[i]] += bd / i;
}
IN int C(R int n, R int m)
{
	int ret = 1;
	std::memset(up, 0, sizeof(up));
	std::memset(down1, 0, sizeof(down1));
	std::memset(down2, 0, sizeof(down2));
	solve(n, up), solve(m, down1), solve(n - m, down2);
	for (R int i = 1; i <= pcnt; ++i) ret = 1ll * ret * fpow(pri[i], up[i] - down1[i] - down2[i]) % mod;
	return ret;
}
int main(void)
{
	scanf("%d%d", &n, &mod); sieve();
	printf("%d", (C(2 * n, n) - C(2 * n, n - 1) + mod) % mod);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值