HNOI2009有趣的數列

首先next_permutation打表,發現Cat規律。ios

其實考試的時候這麼作沒什麼問題,並且能夠節省異常多的時間,那麼如今咱們來想一下why。算法

首先我拿模型法解釋一下,咱們把2n個數當作2n我的,既然分紅奇數和偶數兩種比較方式,那麼我讓他們站成兩排,每一排有n我的,這n我的的身高遞增,且,第二排的人必須高於第一排,那麼這個問題就變成了:ide

有2n個身高互不相同人站成兩排,每排n人,要求右邊的人比左邊的人高,後面的人比前面的人高,問我有幾種排隊方案。函數

這是一個Cat的模型,既然先站哪一排無所謂,我就讓某個位置必須先站上第一排的人再站上第二排的人,若是我將站在第一排看作是0,站在第二排看作是1,那麼既然每一個1前面必定有一個比他矮的人,則必定有一個0,那麼就又轉化成了求0,1序列,這是一個更加經典的Cat模型。(若是這裏理解不了能夠上網搜搜)spa

而後再拿折線法解釋一下,咱們把偶數項看作x軸上的數,由於他們是單增的,把奇數項看作y軸上的數,因爲奇數項小於與之對應偶數項,也就是不能越過y=x,函數的變化就好像只能向右走和向上走。這個問題在上一篇博客中有詳細的解法。調試

因此咱們明白它是讓咱們求Cat,但是P不必定是質數,逆元的問題很噁心。code

因此咱們採用惟一分解來作。首先線性篩篩出2n之內的全部素數,而後咱們枚舉每一個素數,對n執行如下操做:將n不斷的除以這個素數,並將商加入s變量,最終s的值就是n!在算術基本定理拆分後,這個素數的指數。舉個例子:blog

8!=27*32*5*7,8/2=4,4/2=2,2/2=1,1/2=0。4+2+1+0=7。博客

20!=218……,20/2=10,10/2=5,5/2=2,2/2=1,1/2=0。10+5+2+1+0=18。string

你們能夠本身隨便試兩個。

這是爲何呢?(下述i爲質數)首先1~n中含有i這個因子的數有n/i個(1),含有i2這個因子的數有n/i2個(2),……含有im這個因子的數有n/im個(m)。那麼咱們分層計算貢獻,首先(1)中有n/i個i,加上,(2)中有2*n/i2個i,但不要忘了,咱們在(1)算過每一個數中的一個i,那麼它們的貢獻只有n/i2個i,同理,向後類推,最後n!中i的個數爲∑n/pi,與上述模擬過程一致。

那麼咱們來證實一下複雜度,首先根據小於N的質數約有N/lnN個,咱們第一層枚舉的代價就是O(N/lnN),而後觀察上述過程,咱們的問題規模不斷縮小,如上述二例,都是1/二、1/2的速度在縮小,對於其餘素數相似,咱們取最壞O(log2N),那麼總複雜度

O(N/lnN*log2N),這玩意換換底就是O(N/ln2),1/ln2≈1.44,撇掉,大約O(N),(這是我本身證的,網上目測沒有,若是有異議請指出,應該沒什麼問題吧……)

而後分子加分母減拆完了拿快速冪一乘就完事了。(快速冪並不影響上述複雜度,由於qpow也是O(logk)的,就當常數大了吧)。

(底下代碼有表機,勾掉的調試略多,能夠用來本身見證一下上面那個算法的正確性)

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
using namespace std;
int n,P,/*a[20],*/ans=1;
/*bool check(){
    for(int i=1;i<=n;i++)
        if(a[i*2-1]>a[i*2]) return 0;
    for(int i=3;i<=n*2;i++)
        if(a[i]<a[i-2]) return 0;
    return 1;
}*/
int prime[6000000],prime_num;
bool v[20050000];
void doprime(){
    for(int i=2;i<=2*n+5;i++){
        if(!v[i]) prime[++prime_num]=i;
        for(int j=1;j<=prime_num&&i*prime[j]<=2*n+5;j++){
            v[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}
int qpow(int x,int k){
    int val=1;
    for(;k;k>>=1,x=1ll*x*x%P)
        if(k&1) val=1ll*val*x%P;
    return val%P;
}
int main(){
    //打表找規律系列。。。
/*    while(1){
        ans=0;
    scanf("%d%d",&n,&P);
    for(int i=1;i<=(n<<1);i++)
        a[i]=i;
    do{
        if(check()) {ans++;
            for(int i=1;i<=2*n;i++)
                cout<<a[i]<<" ";
            cout<<endl;
        }
    }while(next_permutation(a+1,a+1+2*n));
    printf("ANS=%d\n",ans);
    }*/
    scanf("%d%d",&n,&P);
    doprime();
    for(int i=1;i<=prime_num;i++){
        long long s=0;
        for(int j=2*n;j/=prime[i];) s+=j;
    //    cout<<"s1="<<s<<endl;
        for(int j=n;j/=prime[i];) s-=j;
        //cout<<"s2="<<s<<endl;
        for(int j=n+1;j/=prime[i];) s-=j;
    //    cout<<"s3="<<s<<endl;
        ans=1ll*ans*qpow(prime[i],s)%P;
    }
//    cout<<"Okprime"<<endl;
/*    for(int i=1;i<=prime_num;i++)
        cout<<prime[i]<<" ";cout<<endl;*/
/*    for(int i=1;i<=2*n;i++)
        mulfz(i);
    for(int i=1;i<=n;i++)
        mulfm(i);
    for(int i=1;i<=n+1;i++)
        mulfm(i);*/
/*    cout<<"OKfenjie"<<endl;
    for(int i=1;i<=prime_num;i++)
            ans=1ll*ans*qpow(prime[i],fz[i]-fm[i])%P;
    cout<<"Okqpow"<<endl;*/
    printf("%d",ans);
    return 0;
}
View Code

這道題取模,下道題高精。

相關文章
相關標籤/搜索