Loading

「刷题记录」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;
}
posted @ 2022-07-24 19:04  yi_fan0305  阅读(62)  评论(0编辑  收藏  举报