yifan0305

HNOI2009 有趣的数列

题目传送门:有趣的数列

前言

我翻了好多题解,每一篇题解都只是一部分比较详细,于是想综合一下,所以就有了这篇题解
第一次用markdown

进入正题

首先,一个数列,有2n个数,偶数位上的数比它前面的奇数位上的数大,奇数位上的数是单调递增的,偶数位上的数也是单调递增的
\(a_1<a_3<a_5<a_7......\)\(a_{2i-1}<a_{2i}\)可以得知,每一个偶数位上的数都比它左边的数要大
现在,给你2n个数,已经按升序排好序了,让你把这些数填入这个数列,你会怎么填?
奇数位上的数和偶数位上的数都是单调递增的,偶数位还比奇数位大,所以我们肯定把小的数填入奇数位上,把大的数填入偶数位上,先填小的,再填大的,所以偶数位的填入次数小于等于奇数位的填入次数
看到这,你又想起什么吗?
这不就是卡特兰数吗?例题
这里偶数填入次数相当于出栈次数,奇数填入次数相当于入栈次数
卡特兰数公式:$$f_n=\frac{C_{2n}^n}{n+1}$$

卡特兰数除法的模运算

我们已经找到公式了,但是题目中还有一个取模操作
要知道模运算是不能用在除法中的,而p又不一定是质数(题目没说),所以我们不能求逆元
怎么办呢?
他不让用除法,我把除法转化成符合模运算规则的其他运算不就行了!
这里\(f_n=\frac{C_{2n}^n}{n+1}\)可以继续往下推

\[\begin{aligned} f_n&=\frac{C_{2n}^n}{n+1}\\ &=\frac{2n!}{n!*n!}*\frac{1}{n+1}\\ &=\frac{2n*(2n-1)*(2n-2)*...*(n+2)*n*(n-1)*...*1}{n!*n!}\\ &=\frac{2n*(2n-1)*(2n-2)*...*(n+2)*n!}{n!*n!}\\ &=\frac{2n*(2n-1)*(2n-2)*...*(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;
}

分类:

题解

技术点:

相关文章: