1. 程式人生 > >SDOI 2010--古代豬文(Lucas算法&費馬小定理&中國剩余定理)

SDOI 2010--古代豬文(Lucas算法&費馬小定理&中國剩余定理)

費馬小定理 答案 nbsp ont using main long long 資料 合數

發現幾乎每次數論題洛谷總是讓我TLE一個點。。。。

附圖:

技術分享

最後那個點優化了很久終於過了。。。。

技術分享

題意

iPig在大肥豬學校圖書館中查閱資料,得知遠古時期豬文文字總個數為N。當然,一種語言如果字數很多,字典也相應會很大。當時的豬王國國王考慮到如果修一本字典,規模有可能遠遠超過康熙字典,花費的豬力、物力將難以估量。故考慮再三沒有進行這一項勞豬傷財之舉。當然,豬王國的文字後來隨著歷史變遷逐漸進行了簡化,去掉了一些不常用的字。

iPig打算研究古時某個朝代的豬文文字。根據相關文獻記載,那個朝代流傳的豬文文字恰好為遠古時期的k分之一,其中k是N的一個正約數(可以是1和N)。不過具體是哪k分之一,以及k是多少,由於歷史過於久遠,已經無從考證了。iPig覺得只要符合文獻,每一種能整除N的k都是有可能的。他打算考慮到所有可能的k。顯然當k等於某個定值時,該朝的豬文文字個數為N / k。然而從N個文字中保留下N / k個的情況也是相當多的。iPig預計,如果所有可能的k的所有情況數加起來為P的話,那麽他研究古代文字的代價將會是G的P次方。

現在他想知道豬王國研究古代文字的代價是多少。由於iPig覺得這個數字可能是天文數字,所以你只需要告訴他答案除以999911659的余數就可以了。

輸入格式:

輸入文件有且僅有一行:兩個數N、G,用一個空格分開。

輸出格式:

輸出文件有且僅有一行:一個數,表示答案除以999911659的余數。

Solution

經過幾天的數論的專題練習,我很快看出這道題要求的式子:

ans=(GΣC(k,n) (k是n的因數)

)%999911659;

可以用費馬小定理簡化:

ans=(GΣC(k,n) (k是n的因數)%999911658)%999911659;

因為999911658不是質數,不能直接用Lucas算法,所以分解質因數999911658=2*3*4679*35617;

分別用Lucas算法再用中國剩余定理合並。。。。

過程就不多講了,之前的帖子裏都說過。。。

於是就在BZOJ上通過了。。。

代碼如下

#include<iostream>
#include<cstdio>
#include<map>
#include<cmath>
#define LL long long
#define mod 999911659
#define mod2 999911658
using namespace std;
map<LL,LL> mp;
LL ny[5],a[5];
LL jc[5][20010];
LL pow(LL a,LL b,LL p){
    LL s=1;
    while(b){
        if(b&1)
            s=s*a%p;
        b>>=1;
        a=a*a%p;
    }
    return s;
}
LL C(LL a,LL b,LL p){
    if(b>a) return 0;
    if(b*2>a) b=a-b;
    LL s=1;
    for(int i=1;i<=b;i++){
        LL u=(a+i-b)%p;
        s=s*u%p*jc[mp[p]][i]%p;
    }
    return s;
}
LL Lucas(LL a,LL b,LL p){
    if(b==0) return 1;
    return C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
}
int main(){
    LL n,g,ans=0;
    ny[1]=1;ny[2]=1;ny[3]=1353;ny[4]=31254;
    a[1]=2;a[2]=3;a[3]=4679;a[4]=35617;
    mp[2]=1;mp[3]=2;mp[4679]=3;mp[35617]=4;
    for(int i=1;i<=4;i++) jc[i][0]=1;
    for(int i=1;i<=4;i++){
        for(int j=1;j<=20000;j++)
            jc[i][j]=pow(j,a[i]-2,a[i]);
    }
    scanf("%lld%lld",&n,&g);
    if(g%mod==0) {
        printf("0\n");
        return 0;
    }
    for(int i=1;i<=sqrt(n);i++)
        if(n%i==0){
            if(i*i==n) 
                for(int j=1;j<=4;j++){
                    LL s=ny[j]*Lucas(n,i,a[j]);
                    for(int k=1;k<=4;k++)
                        if(k!=j) s=s*a[k]%mod2;
                    ans=(ans+s)%mod2;
                }
            else
                for(int j=1;j<=4;j++){
                    LL s=ny[j]*Lucas(n,i,a[j]);
                    LL s2=ny[j]*Lucas(n,n/i,a[j]);
                    for(int k=1;k<=4;k++)
                        if(k!=j){
                            s=s*a[k]%mod2;
                            s2=s2*a[k]%mod2;
                        }
                    ans=(ans+s+s2)%mod2;
                }
        }
    ans=pow(g,ans,mod);
    printf("%lld\n",ans);
    return 0;
}

然而這在洛谷上就會像之前圖片上一樣,會T掉一個點。。。。

發現組合數的函數經常調用,裏面還有for循環很耗時。。。

於是稍作優化(woc搞了一上午好吧。。。)

至於是怎麽優化的也自己看吧。。。現在很心累。。。

前後對比圖:

技術分享

代碼

#include<iostream>
#include<cstdio>
#include<map>
#include<cmath>
#define LL long long
#define mod 999911659
#define mod2 999911658
using namespace std;
map<LL,LL> mp;
LL ny[5],a[5],top;
LL jc[5][40010];

LL pow(LL a,LL b,LL p){
    LL s=1;
    while(b){
        if(b&1)
            s=s*a%p;
        b>>=1;
        a=a*a%p;
    }
    return s;
}
LL C(LL a,LL b,LL p) {
    if (a<b) return 0;
    return jc[mp[p]][a]*pow(jc[mp[p]][b]*jc[mp[p]][a-b],p-2,p)%p;
}
LL Lucas(LL a,LL b,LL p){
    if(!b) return 1;
    return C(a%p,b%p,p)*Lucas(a/p,b/p,p)%p;
}
int main(){
    LL n,g,ans=0;
    ny[1]=1;ny[2]=1;ny[3]=1353;ny[4]=31254;
    a[1]=2;a[2]=3;a[3]=4679;a[4]=35617;
    mp[2]=1;mp[3]=2;mp[4679]=3;mp[35617]=4;
    for (int i=1;i<=4;i++) {
        jc[i][0]=1;
        for (int j=1;j<=a[i];j++)
            jc[i][j]=jc[i][j-1]*j%a[i];
    }
    scanf("%lld%lld",&n,&g);
    g=g%mod;
    if(!g) {
        printf("0\n");
        return 0;
    }
    LL d=sqrt(n);
    for(int i=1;i<=d;i++)
        if(n%i==0){
            if(i*i==n) 
                for(int j=1;j<=4;j++){
                    LL s=ny[j]*Lucas(n,i,a[j]);
                    s=mod2/a[j]*s%mod2;
                    ans=(ans+s)%mod2;
                }
            else
                for(int j=1;j<=4;j++){
                    LL s=ny[j]*Lucas(n,i,a[j]);
                    LL s2=ny[j]*Lucas(n,n/i,a[j]);
                    s=mod2/a[j]*(s+s2)%mod2;
                    ans=(ans+s)%mod2;
                }
        }
    ans=pow(g,ans,mod);
    printf("%lld\n",ans);
    return 0;
}

This passage is made by Yukino.

SDOI 2010--古代豬文(Lucas算法&費馬小定理&中國剩余定理)