现在的位置: 首页 > 综合 > 正文

关于卡特兰数

2017年10月16日 ⁄ 综合 ⁄ 共 3075字 ⁄ 字号 评论关闭

开始的时候了解过卡特兰数,但是不会做题,只会套公式……好吧……略过不提。

题意:给你n和m,叫你求卡特兰数h[n]%m的结果。

首先,公式h[n]=h[n-1]*(4*n-2)/(n+1)【注意这里n从0开始,是式子中的某一项,所以在应用实例中要记得进行区分(此n非彼n)】

由于数据很大,要取模,但是因为取模后分母就不一定整除分子了,所以不能对于结果直接取模,要分别进行。【其实所以结果可能是个小数?那怎么可以取模……】所以对分子取模的时候要求他的乘法逆元,但是乘法逆元必须是两个数要互质才可以,所以还要进行因数分解。【嗯,其实对于整个过程还不是太了解的说,但是大体能懂=  =】。将分子和分母都进行分解,分解成与m互质和不与m互质的两部分。对于分母,互质的直接相乘,然后取模,对于分子,消掉和分母相同的部分,剩下的就是和m互质的那一部分了。然后进行扩展欧几里得求逆元。然后估计这里保存的分母的因子是一路计算来的因数的个数(幂数),所以一定会让分子只剩和m互质的部分,,,,,啊,其实因为对于公式,一定算出来是个整数【虽然没有证为什么】,但是由于取模了所以就可能不能整除了,所以在这里对分母进行分解,保留其幂数,然后在对分子的运算过程就可以借此消掉,好让结果还是个整数【虽然依旧没有证为什么……】

写出来主要是因为想了快两个小时才稍稍摸到门道,不想思路就这样没有了,然后……等我继续更新吧……这道题一定要A掉……

【诶,感觉自己蠢爆了=  =】

【嗯,然后记得提醒自己,想不通就是想不通,好好想,不要以“可能是哪里没学过”这样的理由东看西看浪费时间,勇于尝试勇于想】

这是第一份代码,答案是对的,但是TLE了,试了一下数据,确实是跑得慢了,怎么优化都没有用。

#include <stdio.h>
#include <string.h>
#define maxn 500100
#define ll __int64
ll num[maxn],prim[maxn];
int fint[maxn];
int top;
int n,m;
/*void fint()
{
    int i,j,k;
    memset(su,1,sizeof(su));
    su[0]=su[1]=0;
    for(i=2;i<maxn;i++)
    {
        if(su[i])
            for(j=i+i;j<maxn;j+=i)
            su[i]=0;
    }
}*/
void exgcd(ll a,ll b,ll &x,ll &y)  ///模板
{
     if(b==0)
     {
          x=1;
          y=0;
     }
     else
     {
          ll t;
          exgcd(b,a%b,x,y);
          t=x; x=y;
          y=t-(a/b)*y;
     }
}

void cal1(ll &ans,ll x)
{
    ll i,j,k;
    j=x;
    for(i=0;i<top;i++)
    {
        while(j%prim[i]==0)
        {
            j/=prim[i];
            num[i]++;
        }
    }
    ans=(ans)*(j);
    ans%=m;
}
void cal2(ll &ans,ll x)  ///取模可以对
{
    int i,j,k;
    j=x;
    for(i=0;i<top;i++)
    {
        while((j%prim[i]==0)&&num[i]>0)
        {
            j/=prim[i];
            num[i]--;
        }
    }
    if(j>1)
    {
        ll xx,yy;
        exgcd(j,m,xx,yy);
        xx=(xx%m+m)%m;    ///这一步很重要,有的时候逆元算出来貌似是负数,加这一步可以避免
        ans=(ans*xx)%m;
    }
}
ll quickpow(ll m,ll n,ll k)
{
    ll b = 1;
    while (n > 0)
    {
          if (n & 1)
             b = (b*m)%k;
          n = n >> 1 ;
          m = (m*m)%k;
    }
    return b;
}
int main()
{
    //int n,m;
    freopen("out.txt","w",stdout);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==1||n==2) {printf("0\n");continue;}
       // memset(num,0,sizeof(num));
        n-=2;
        ll i,j,k;
        j=m;
        top=0;
        for(i=2;i*i<=m;i++)
        {
            if(j%i==0)
            {
                prim[top++]=i;
                while(j%i==0)
                    j/=i;
            }
        }
        if(j>1) prim[top++]=j;
        for(i=0;i<top;i++)
            num[i]=0;
        //h[1]=1;//h[2]=2;h[3]=5;
        ll ans=1;
        ll tmp;
        for(i=2;i<=n;i++)
        {
            cal1(ans,4*i-2);
            cal2(ans,i+1);
        }
            tmp=ans;
            for(j=0;j<top;j++)
            {
                tmp*=quickpow(prim[j],num[j],m);  ///这里改成用快速幂
                /*for(k=0;k<num[j];k++)
                    tmp=(tmp*prim[j])%m;*/
            }
        printf("%I64d\n",tmp);
    }
    return 0;
}

嗯……学习了一下标程……

没有求逆元,没有递推式。

用的公式是这一种:h[n]=(2n!)/(n!*(n+1)!)

但是不知道为什么对于n的阶乘的因子分解是这样做的……效率高很多。然后素数记得开得大一点。

#include <stdio.h>
#include <string.h>
#define maxn 1000100
int prime[maxn],que[maxn];
int top;
int div(int x,int y)  ///我不懂的因子分解……
{
    int r=0;
    //int tmp=x;
    while(x)
    {
        r+=x/=y;
    }
    return r;
}
void fint()
{
    int i,j,k;
    memset(prime,1,sizeof(prime));
    prime[0]=prime[1]=0;
    for(i=2;i<maxn;i++)
    {
        if(prime[i])
            for(j=i+i;j<maxn;j+=i)
            prime[j]=0;
    }
    for(i=2;i<maxn;i++)
        if(prime[i]) que[top++]=i;
}
int ppow(int a,int b,int m){
int r=1,t=a%m;
    while(b){
    	if(b&1)r=(r*t)%m;
    	t=(t*t)%m;
    	b>>=1;
    }
	return r;
}
int solve(int n,int m)
{
    int i,j,k;
    int nn=n<<1;
    //fint();
    int r=1;
    for(i=0;i<top&&que[i]<=nn&&r;i++)
    {
        //printf("**%d\n",que[i]);
        j=div(nn,que[i]);//printf("%d  ",j);
        j-=div(n,que[i]);//printf("%d  ",j);
        j-=div(n+1,que[i]);//printf("%d\n",j);
        r=(r*ppow(que[i],j,m))%m;
       // printf("%d\n",r);
    }
    return r;
}
int main()
{
    int n,m;
    top=0;
    fint();
    //printf("%d\n",top);
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        printf("%d\n",solve(n-2,m));
    }
    return 0;
}

****************************************

估摸着需要鉴别一下两个公式的使用处。

第一个递推式就适合要求过程和的。

第二个式子就适合要求单个结果的。

【上篇】
【下篇】

抱歉!评论已关闭.