[HNOI2009]有趣的数列
Description
我们称一个长度为2n的数列是有趣的,当且仅当该数列满足以下三个条件:
(1)它是从1到2n共2n个整数的一个排列{ai};
(2)所有的奇数项满足a1<a3<…<a2n-1,所有的偶数项满足a2<a4<…<a2n;
(3)任意相邻的两项a2i-1与a2i(1≤i≤n)满足奇数项小于偶数项,即:a2i-1<a2i。
现在的任务是:对于给定的n,请求出有多少个不同的长度为2n的有趣的数列。因为最后的答案可能很大,所以只要求输出答案 mod P的值。
Input
输入文件只包含用空格隔开的两个整数n和P。输入数据保证,50%的数据满足n≤1000,100%的数据满足n≤1000000且P≤1000000000。
Output
仅含一个整数,表示不同的长度为2n的有趣的数列个数mod P的值。
Sample Input
3 10
Sample Output
5
对应的5个有趣的数列分别为(1,2,3,4,5,6),(1,2,3,5,4,6),(1,3,2,4,5,6),(1,3,2,5,4,6),(1,4,2,5,3,6)。
题解
法一:
找规律发现前几项为卡特兰数列,再根据快速计算组合数的方法进行计算
f ( n ) =C(n, 2n)/(n+1) = (2n)!/(n!*(n+1)!)
AC代码:
#include <bits/stdc++.h>//[HNOI2009]有趣的数列
#define LL long long
using namespace std;
const LL MAXN = 1000005;
LL n, p, cnt;
bool v[MAXN];
LL primes[2 * MAXN];
bool isHeshu[2 * MAXN];
LL oula(LL x); //质数筛
LL qkpow(LL x, LL y); //快速幂
int main()
{
scanf("%d%d", &n, &p);
oula(2 * n);
LL ans = 1;
for (LL i = 1; i <= cnt; ++i)
{
LL cnt1 = 0, cnt2 = 0, cnt3 = 0;
LL pm = primes[i];
while (pm <= 2 * n)
{
cnt1 += n / pm;
cnt2 += (n + 1) / pm;
cnt3 += 2 * n / pm;
pm *= primes[i]; //当前质因子次数+1
}
LL sl = cnt3 - cnt1 - cnt2;
ans *= qkpow(primes[i], sl);
ans %= p;
}
printf("%lld\n", ans);
return 0;
}
LL oula(LL x)
{
for (LL i = 2; i <= x; ++i)
{
if (!isHeshu[i])
{
primes[++cnt] = i;
}
for (LL j = 1; primes[j] * i <= x; ++j)
{
isHeshu[primes[j] * i] = 1;
if (i % primes[j] == 0)
break;
}
}
}
LL qkpow(LL x, LL y)
{
LL sum = x;
LL ret = 1;
while (y)
{
if (y & 1)
{
ret = ret * sum % p;
}
sum = sum * sum % p;
y >>= 1;
}
return ret;
}
快速计算组合数的方法:
对于本题中f ( n ) =C(n, 2n)/(n+1) = (2n)!/(n!*(n+1)!) ,可以枚举1~2n之间的素数然后计算这些素数分别出现的次数 , 消掉部分 , 再将剩余的利用快速幂算法乘进答案中
for (LL i = 1; i <= cnt; ++i)
{
LL cnt1 = 0, cnt2 = 0, cnt3 = 0;
LL pm = primes[i];
while (pm <= 2 * n)
{
//找相应的素数出现的次数
cnt1 += n / pm;
cnt2 += (n + 1) / pm;
cnt3 += 2 * n / pm;
pm *= primes[i]; //当前质因子次数+1
}
LL sl = cnt3 - cnt1 - cnt2;
ans *= qkpow(primes[i], sl);
ans %= p;
}
LL qkpow(LL x, LL y)//快速幂
{
LL sum = x;
LL ret = 1;
while (y)
{
if (y & 1)
{
ret = ret * sum % p;
}
sum = sum * sum % p;
y >>= 1;
}
return ret;
}
法二DP:
如果确定了奇数项,那么数列要么不合法,要么唯一确定 , 继续观察后发现奇数项只要满足ai<2i即可
, 故可以得到一个n^2的dp
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define ll long long
#define pa pair<int,int>
#define mod 1000000007
#define inf 100000000
using namespace std;
int read()
{
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
int n,P;
int f[1005][2005],s[1005][2005];//f[i][j]表示前i位填到数字j的方案,即第i位用的是j,用前缀和加速转移
int main()
{
n=read();P=read();
f[0][0]=1;
for(int i=0;i<=2*n;i++)s[0][i]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=2*i-1;j++)
{
f[i][j]=s[i-1][j-1]%P;
}
for(int j=1;j<=2*n;j++)
s[i][j]=(s[i][j-1]+f[i][j])%P;
}
printf("%d",s[n][2*n]);
return 0;
}