「刷题记录」HNOI2009 有趣的数列
题目传送门:有趣的数列
前言
我翻了好多题解,每一篇题解都只是一部分比较详细,于是想综合一下,所以就有了这篇题解
第一次用 \(\text{markdown}\)
进入正题
首先,一个数列,有 \(2n\) 个数,偶数位上的数比它前面的奇数位上的数大,奇数位上的数是单调递增的,偶数位上的数也是单调递增的
由 $a_1 < a_3 < a_5 < a_7 ...... $ 和 \(a_{2i - 1} < a_{2i}\) 可以得知,每一个偶数位上的数都比它左边的数要大
现在,给你 \(2n\) 个数,已经按升序排好序了,让你把这些数填入这个数列,你会怎么填?
奇数位上的数和偶数位上的数都是单调递增的,偶数位还比奇数位大,所以我们肯定把小的数填入奇数位上,把大的数填入偶数位上,先填小的,再填大的,所以偶数位的填入次数小于等于奇数位的填入次数
看到这,你又想起什么吗?
这不就是卡特兰数吗?例题
这里偶数填入次数相当于出栈次数,奇数填入次数相当于入栈次数
卡特兰数公式:$$f_n=\dfrac{C_{2n}^n}{n+1}$$
卡特兰数除法的模运算
我们已经找到公式了,但是题目中还有一个取模操作
要知道模运算是不能用在除法中的,而 \(p\) 又不一定是质数(题目没说),所以我们不能求逆元
怎么办呢?
他不让用除法,我把除法转化成符合模运算规则的其他运算不就行了!
这里\(f_n=\dfrac{C_{2n}^n}{n+1}\)可以继续往下推
\[\begin{aligned}
f_n&=\dfrac{C_{2n}^n}{n+1}\\
&=\dfrac{2n!}{n! \times n!} \times \dfrac{1}{n+1}\\
&=\dfrac{2n \times (2n-1) \times (2n-2) \times ... \times (n+2) \times n \times (n-1) \times ... \times 1}{n! \times n!}\\
&=\dfrac{2n \times (2n-1) \times (2n-2) \times ... \times (n+2) \times n!}{n! \times n!}\\
&=\dfrac{2n \times (2n-1) \times (2n-2) \times ... \times (n+1)}{n!}
\end{aligned}
\]
\(2n~(n+2)\) 都是 \(1\) 次幂,\(n\)!都是 \(-1\) 次幂,我们可以继续拆分一下,将和数拆分成质数次幂相乘的形式,将次幂下传,用快速幂来处理乘方
现在,除法已经被我们转化成了乘法,只要对每一个因数取模就行了
上代码
#include<iostream>
#include<cstdio>
#include<vector>
typedef long long ll;
using namespace std;
inline ll read()
{
ll x=0;
bool fg=false;
char ch=getchar();
while(ch<'0'||ch>'9')
{
fg|=(ch=='-');
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return fg?~x+1:x;
}
const int N=2e6+5;
int n,mod;
int zs[N],mp[N];//zs 指数 mp 最小质因数
vector<int> prime;//vector存质数
ll qpow(int x,int y,int p)//快速幂
{
ll ans=1;
while(y)
{
if(y&1) ans=(ans*x)%p;
y>>=1;
x=1ll*(x*x)%p;
}
return ans%p;
}
void make_prime(int x)//欧拉筛
{
for(int i=2;i<=x;++i)
{
if(!mp[i])//记录最小质因数
{
prime.push_back(i);//存入
prime[0]++;//个数++
mp[i]=i;//质数的最小质因数是它本身
}
for(int j=1;j<=prime[0]&&prime[j]*i<=x;++j)
{
mp[i*prime[j]]=prime[j];//更新和数的最小质因数
if(i%prime[j]==0) break;
}
}
}
int main()
{
n=read(),mod=read();
prime.push_back(0);//prime第0号元素记录个数
make_prime(n<<1);//筛素数
for(int i=2;i<=n;++i) zs[i]=-1;
for(int i=n+2;i<=n<<1;++i) zs[i]=1;//更新指数
for(int i=n<<1;i>=2;--i)//质因数越除越小,从大到小可以避免更新遗漏,从而达到线性
{
if(mp[i]<i)//如果是合数,那就质因数分解,将指数下放给自己的质因数
{
zs[mp[i]]+=zs[i];
zs[i/mp[i]]+=zs[i];//下放标记
}
}
ll ans=1;//不能为0
for(int i=2;i<=n<<1;++i)//将原本的除法转化成质因数分解
{
if(mp[i]==i)//此时和数都已经下放了自己的指数,所以只有质数还有指数
ans=1ll*ans*qpow(i,zs[i],mod)%mod;//记得随时取模
}
printf("%lld\n",ans%mod);
return 0;
}
朝气蓬勃 后生可畏