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

来源:互联网 发布:禅道下载安装linux 编辑:程序博客网 时间:2024/06/07 02:09

题解

首先%%%谢大佬 感谢她给我讲了这道题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<cstdio>#include<algorithm>using namespace std;const int N=1000000+5;#define ll long longint 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;}
原创粉丝点击