BZOJ-1485 [HNOI2009]有趣的数列(卡特兰数+阶乘分解)
题目描述
我们称一个长度为 \(2n\) 的数列是有趣的,当且仅当该数列满足以下三个条件:
-
它是从 \(1\) ~ \(2n\) 共 \(2n\) 个整数的一个排列 \(a_i\)
-
所有的奇数项满足 \(a_1<a_3<\cdots<a_{2n-1}\),所有的偶数项满足 \(a_2<a_4<\cdots<a_{2n}\)
-
任意相邻的两项 \(a_{2i-1}\) 与 \(a_{2i}\) 满足:\(a_{2i-1}<a_{2i}\)。
对于给定的 \(n\),求有多少个不同的长度为 \(2n\) 的有趣的数列,答案对 \(p\) 取模。
数据范围:\(1\leq n\leq 10^6,1\leq p\leq 10^9\)。
分析
由于偶数项递增,且偶数项大于相邻奇数项,即偶数项大于前面所有的数字,也就是说偶数项的数字大于等于偶数项的下标。
考虑按照 \(1\) ~ \(2n\) 的顺序依次放数字,可以发现每次只能放在下标最小的奇数或偶数位上,这样才能保证数字递增。假设当前在偶数位放置了 \(a\) 个数字,在奇数位放了 \(b\) 个数字,且 \(a>b\),一共放了 \(x=a+b\) 个数。则 \(a>\frac{x}{2}\),最后放置的偶数的下标为 \(2a\),但是 \(2a>x\),与假设冲突,因此假设不成立。所以 在任意时刻放入偶数位的数字个数都不能超过放入奇数位的个数,这就是卡特兰数。
卡特兰数通项公式:
由于模数 \(p\) 不一定是质数,所以不能直接求逆元。首先筛出 \(1\) ~ \(2n\) 中的所有质数,枚举每个质数,计算 \(cnt1\):\(1\) ~ \(2n\) 中包含这个质因子的数的数量,\(cnt2\):\(1\) ~ \(n+1\) 中包含这个质因子的数的数量,\(cnt3\):\(1\) ~ \(n\) 中包含这个质因子的数的数量。则 \(cnt1-cnt2-cnt3\) 即为答案中包含这个质因子的数的数量。
接下来考虑如何快速分解 \(1\) ~ \(n\) 中的质因子的数量:
如果把 \(1\) ~ \(n\) 中的每个数都分解质因数,再把结果合并,时间复杂度为 \(O(n\sqrt{n})\)。\(n!\) 中质因子 \(p\) 的个数等于 \(1\) ~ \(n\) 中每个数包含质因子 \(p\) 的个数之和。在 \(1\) ~ \(n\) 中,\(p\) 的倍数显然有 \(\lfloor\frac{n}{p}\rfloor\) 个,\(p^2\) 的倍数有 \(\lfloor\frac{n}{p^2}\rfloor\) 个,\(\cdots\)。
综上所述,\(n!\) 中质因子 \(p\) 的个数为:
时间复杂度 \(O(n\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;
long long n,p;
int prime[N+10],vis[N+10],cnt;
void init()
{
for(int i=2;i<=N;i++)
{
if(!vis[i])
prime[++cnt]=i;
for(int j=1;i*prime[j]<=N&&j<=cnt;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
}
long long quick_pow(long long a,long long b,long long p)
{
long long ans=1;
while(b)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
long long divide(long long n,long long p)
{
long long ans=0;
while(n)
{
ans=ans+n/p;
n=n/p;
}
return ans;
}
int num[N];
int main()
{
init();
cin>>n>>p;
for(int i=1;i<=cnt;i++)
num[i]=divide(2*n,prime[i])-divide(n+1,prime[i])-divide(n,prime[i]);
long long ans=1;
for(int i=1;i<=cnt;i++)
ans=ans*quick_pow(prime[i],num[i],p)%p;
cout<<ans<<endl;
return 0;
}
posted on 2020-12-06 15:20 DestinHistoire 阅读(88) 评论(0) 收藏 举报