889. 满足条件的01序列
给定 n
个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1
的个数的序列有多少个。
输出的答案对 109+7
取模。
输入格式
共一行,包含整数 n
。
输出格式
共一行,包含一个整数,表示答案。
数据范围
1≤n≤105
输入样例:
3
输出样例:
5
* 假定在一个二维矩阵的地图里,向右走为0,向上走为1,则要求能排列成的所有序列中,
* 能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列,所有点都满足为或者在
* (0,0) 到 (n,n) 的直线上或者在下方。只要有点越过直线 y=x+1 ,再到达点(n,n),
* 则这种排列就是不符合要求的。所以不符合要求的走法一定满足从(0,0) 到 (n-1,n+1) 的
* 路线关于 y=x+1 对称 (因为(0,0) 到 (n,n+1) 的路线一定是不合法的,并且(n,n)与
* (n-1,n+1) 关于 y=x+1 对称。)。那么我们只要 C(2*n,n) - C(2*n,n-1) 就是答案。
*
* C(2*n,n) - C(2*n,n-1) =C(2*n,n)/(n+1);()
*
* 卡特兰数:
* C(2*n,n) - C(2*n,n-1) =(2*n)! / (n! * n! * (n+1))
/**
* 假定在一个二维矩阵的地图里,向右走为0,向上走为1,则要求能排列成的所有序列中,
* 能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列,所有点都满足为或者在
* (0,0) 到 (n,n) 的直线上或者在下方。只要有点越过直线 y=x+1 ,再到达点(n,n),
* 则这种排列就是不符合要求的。所以不符合要求的走法一定满足从(0,0) 到 (n-1,n+1) 的
* 路线关于 y=x+1 对称 (因为(0,0) 到 (n,n+1) 的路线一定是不合法的,并且(n,n)与
* (n-1,n+1) 关于 y=x+1 对称。)。那么我们只要 C(2*n,n) - C(2*n,n-1) 就是答案。
*
* C(2*n,n) - C(2*n,n-1) =C(2*n,n)/(n+1);()
*
* 卡特兰数:
* C(2*n,n) - C(2*n,n-1) =(2*n)! / (n! * n! * (n+1))
*/
#include <iostream>
using namespace std;
typedef long long LL;
const int mod=1e9+7,maxn=1e5+10;
int fac[2*maxn],infac[maxn];
int qmi(int a,int b,int p)
{
int res=1;
while(b)
{
if(b&1)
res=(LL)res*a%mod;
a=(LL)a*a%mod;
b>>=1;
}
return res;
}
void init(int n)
{
fac[0] = infac[0] = 1; //初始化
for(int i=1;i<=2*n;++i)
fac[i]=(LL)fac[i-1]*i%mod;
for(int i=1;i<=n;++i)
infac[i]=(LL)infac[i-1]*qmi(i,mod-2,mod)%mod; //必须要用qmi求逆元
}
int main()
{
int n;
cin >> n;
init(n);
cout << (LL)fac[2*n]*infac[n]%mod*infac[n]%mod *qmi(n+1,mod-2,mod)%mod << endl;
return 0;
}
130. 火车进出栈问题
一列火车 n
节车厢,依次编号为 1,2,3,…,n
。
每节车厢有两种运动方式,进栈与出栈,问 n
节车厢出栈的可能排列方式有多少种。
输入格式
输入一个整数 n
,代表火车的车厢数。
输出格式
输出一个整数 s
表示 n
节车厢出栈的可能排列方式数量。
数据范围
1≤n≤60000
输入样例:
3
输出样例:
5
* 卡特兰数:
* C(2*n,n) - C(2*n,n-1) =(2*n)! / (n! * n! * (n+1));
*
* 这个题与满足条件的01序列解法一样,只不过这个题是输出全部方案数,不取模,
* 输出高精度数据;由于数据较大,所以我们用质因数分解的方法来求解。
* 最后由于高精度数据相乘是主要的算法时间消耗,因此我们优化高精度数据相乘,
* 用LL来存储结果,每次相乘我们以 M=1e9 作为进制数,(这样两个M相乘也不会爆LL);
* 这样能减少许多乘法步骤;这种方法称为高精度压位。
/**
* 假定在一个二维矩阵的地图里,向右走为0,向上走为1,则要求能排列成的所有序列中,
* 能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列,所有点都满足为或者在
* (0,0) 到 (n,n) 的直线上或者在下方。只要有点越过直线 y=x+1 ,再到达点(n,n),
* 则这种排列就是不符合要求的。所以不符合要求的走法一定满足从(0,0) 到 (n-1,n+1) 的
* 路线关于 y=x+1 对称 (因为(0,0) 到 (n,n+1) 的路线一定是不合法的,并且(n,n)与
* (n-1,n+1) 关于 y=x+1 对称。)。那么我们只要 C(2*n,n) - C(2*n,n-1) 就是答案。
*
* C(2*n,n) - C(2*n,n-1) =C(2*n,n)/(n+1);()
*
* 卡特兰数:
* C(2*n,n) - C(2*n,n-1) =(2*n)! / (n! * n! * (n+1));
*
* 这个题与满足条件的01序列解法一样,只不过这个题是输出全部方案数,不取模,
* 输出高精度数据;由于数据较大,所以我们用质因数分解的方法来求解。
* 最后由于高精度数据相乘是主要的算法时间消耗,因此我们优化高精度数据相乘,
* 用LL来存储结果,每次相乘我们以 M=1e9 作为进制数,(这样两个M相乘也不会爆LL);
* 这样能减少许多乘法步骤;这种方法称为高精度压位。
*/
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL M=1e9;
const int N = 12e4+10;
int p[N],sum[N],num=0;
bool hs[N];
void get_primer(int n) //获得n以内的素数
{
for(int i=2;i<=n;++i)
{
if(hs[i]==0)
p[num++]=i;
for(int j=0;p[j]<=n/i;++j)
{
hs[p[j]*i]=1;
if(i%p[j]==0)
break;
}
}
}
int cal(int n,int p) //计算n! 内有多少个因子p
{
int cnt=0;
while (n)
{
cnt+=n/p;
n/=p;
}
return cnt;
}
void get_pow(int a,int b) //将C(a,b) 进行质因子分解
{
for(int i=0;i<num;++i)
{
int cnt=cal(a,p[i]) -cal(b,p[i])*2;
sum[i]=cnt;
}
}
void mul(vector<LL> &A,int b) //高精度与整数相乘
{
LL d=0;
for(int i=0;i<A.size();++i)
{
d+=A[i]*b;
A[i]=d%M;
d/=M;
}
while(d)
{
A.push_back(d%M);
d/=M;
}
}
void print(vector<LL> &res) //输出高精度数据
{
cout << res.back(); // 最后一位不需要输出九位
for(int i=res.size()-2;i>=0;--i)
printf("%09lld",res[i]); //因为是按着1e9作为进制数,所以中间的位要输出九位,
cout << endl; //不足九位的补零
}
int main()
{
int n;
cin >> n;
get_primer(2*n);
get_pow(2*n,n);
int r=n+1;
for(int i=0;i<num && r>1;++i)
{
while(r%p[i]==0)
{
sum[i]-=1;
r/=p[i];
}
}
vector<LL> res;
res.push_back(1);
for(int i=0;i<num;++i)
for(int j=0;j<sum[i];++j)
mul(res,p[i]);
print(res);
return 0;
}