bzoj1485 catalan数+特殊的求组合数方法

题解

首先%%%谢大佬 感谢她给我讲了这道题O(n)求组合数的方法
以及在这里贴上谢大佬这道题的题解,因为访问量太低谢大佬都伤心了->谢大佬的良心题解你值得拥有
1、我们可以得出这道题的答案是catalan数 详解见此
2、我们会发现这道题的p是不定的并且它可以不为素数。这时我们就可以引出这种神奇的求组合数方法了——

例:求comb(m,n)%mod

大体思路

先用线性筛筛出每个数的质数以及最小质因数
接下来求cnt[x],cnt[x]意为因数x出现的次数。最终的结果是只有cnt[prime]有值,其他都没有值。我们再for一遍素数用快速幂就可以得出结果了。

求cnt[x]的具体操作

从大到小for(m->1) ,根据该数是在分母还是分子来确定它是+1还是-1(也就是代码中的add(x,-1)还是add(x,1))
if(当i为素数时){cnt[i]++;}
if(当i不为素数时){
cnt[i]++;
cnt[d[i]]+=cnt[i],cnt[i/d[i]]+=cnt[i];
//把cnt[i]的值转移到cnt[d[i]]和cnt[i/d[i]]
cnt[i]=0;
//最后记得清空cnt[i]因为cnt[i]已经转移到cnt[它的因数]去了
}
但是i/d[i]有可能不是素数,没关系,从大到小for,之后又会for到i/d[i],又可以继续分解它了。
最后只有素数i的cnt[i]!=0

时间复杂度分析

处理cnt[i] O(n)
for素数o(n/logn) * 快速幂O(logn)=O(n)
时间复杂度共O(n)

#include
#include
using namespace std;
const int N=1000000+5;
#define ll long long
int n,p;
bool noprime[2*N];int prime[N*2],d[2*N],size,cnt[2*N];
void euler()
{
    d[1]=1;
    for(int i=2;i<=2*n;i++){
        if(!noprime[i]){
            prime[++size]=i;
            d[i]=i;
        } 
        for(int j=1;j<=size&&i*prime[j]<=2*n;j++){
            noprime[i*prime[j]]=1;
            d[i*prime[j]]=prime[j];//!!!!
            if(i%prime[j]==0) break;
        }
    }
}
void add(int x,int opt)
{
    cnt[x]+=opt; 
    if(noprime[x]){
        cnt[d[x]]+=cnt[x];
        cnt[x/d[x]]+=cnt[x];
        cnt[x]=0;
    }
}
ll mpow(int a,int b)
{
    ll base=a,ans=1;
    for(int i=b;i;i>>=1,base=(base*base)%p)
    if(i&1) ans=(ans*base)%p;
    return ans;
}
int main()
{   
    scanf("%d%d",&n,&p);
    euler();
    for(int i=2*n;i>n+1/*catalan*/;i--) add(i,1);
    for(int i=n;i>=1;i--) add(i,-1);//在上一波分解中如果分成了小于n的合数那么这个合数还需要被分 
    ll ans=1;
    for(int i=1;prime[i]<=2*n&&i<=size;i++)
    if(cnt[prime[i]])
        ans=(ans*mpow(prime[i],cnt[prime[i]]))%p;   
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:(------数论-------)