1. 程式人生 > >【模板篇】數論大雜燴~

【模板篇】數論大雜燴~

離省選已經不剩幾天了, 然而自己還是啥也不會, 所以就臨陣磨槍刷一刷板子, 萬一用上了就賺到了~~
這一篇就寫寫數論?(希望只考到自己打的板子然而這是不可能的←_←

儘量壓行吧, 能寫得好懂點就寫得好懂一點吧... (為了省事就全開long long了哈, 卡常題請謹慎複製...)
裡面列舉的內容可能沒啥規律, 想起啥寫啥, 應該會很亂...

先是應該是不用寫的gcd:

LL gcd(LL a,LL b){
    if(!b) return a;
    return gcd(b,a%b);
}

然後是exgcd:

void exgcd(LL a,LL b,LL &x,LL &y){
    if(!b) x=1,y=0;
    else exgcd(b,a%b,y,x),y-=(a/b)*x;
}

快速冪也是炒雞常用的:

LL qpow(LL a,LL b,LL p,LL s=1){
    for(;b;b>>=1,a=a*a%p)
        if(b&1) s=s*a%p;
    return s;
}

然後是跟質數或者辣雞反演有關的題裡幾乎都要用到的線篩(這玩意我還日常打不對的說...)
篩質數的同時寫一下篩\(\mu, \varphi, \sigma_0,\sigma_1\)這些函式(打表找規律的積性函式就不寫了也寫不完)

/*
pr質數陣列 tot質數個數 mu莫比烏斯函式 phi尤拉函式 d約數個數 sd約數和
fd 篩約數個數用的輔助陣列(最小質因子的個數) fsd 篩約數和用的輔助陣列
(最小質因子等比數列的字首和,即 (1+p1+p1^2+p1^3+...+p1^k1))
*/
void shai(){
    np[1]=mu[1]=phi[1]=d[1]=sd[1]=1;
    for(int i=2,k;i<=n;++i){
        if(!np[i]){
            pr[++tot]=i; mu[i]=-1; phi[i]=i-1;
            fd[i]=1; d[i]=2; fsd[i]=sd[i]=i+1;
        }
        for(int j=1;j<=tot&&(k=i*pr[j])<=n;++j){ np[k]=1;
            if(i%pr[j]==0){
                mu[k]=0; phi[k]=phi[i]*pr[j];
                fd[k]=fd[i]+1; d[k]=d[i]/(fd[i]+1)*(fd[i]+2);
                fsd[k]=fd[i]*pr[j]+1;
                sd[k]=sd[i]/fsd[i]*fsd[k];
                break;
            }
            mu[k]=-mu[i]; phi[k]=phi[i]*(pr[j]-1);
            fd[k]=1; d[k]=d[i]*2; fsd[k]=pr[j]+1;
            sd[k]=sd[i]*sd[pr[j]];
        }
    }
}

Emmmm寫個線篩寫了好久最後果然還是出錯了..

逆元相關:
然後是兩種求\(a\)\(mod\ p\)逆元的方法:

// 費馬小定理的a^p-2程式碼就不寫了_(:з」∠)_
// 另一種就是利用exgcd:
LL inv(LL a,LL p){
    if(!a) return 0;
    LL x,y; exgcd(a,p,x,y);
    return (x+p)%p;
}

線性求出所有逆元:

inv[1]=1;
for(int i=2;i<p;++i)
    inv[i]=(p-(p/i))*inv[p%i]%p;

O(1)快速乘(損精度):

LL multi(Ll a,LL b){
    return (a*b-(LL)((long double)a*b/p)*p+p)%p;
}

然後是一發Lucas定理:

// 這裡預設階乘和階乘逆元已經預處理好分別放在fac和inv數組裡了~
LL lucas(LL n,LL m,LL p){
    if(n<m) return 0;
    if(n<p) return fac[x]*inv[y]*inv[x-y];
    return lucas(n/p,m/p,p)*lucas(n%p,m%p,p);
}

再來一波CRT(中國剩餘定理)

//假設模數存在p數組裡, 餘數存在c數組裡, 一共有n個式子, 模數的總乘積是P.
void CRT(LL ans=0){
    for(int i=0;i<n;++i)
        ans=(ans+c[i]*(P/p[i])%P*inv(P/p[i],p[i])%P)%P;
    return ans;
}

突然發現自己還沒寫過擴充套件CRT哇, 趕緊新寫一發~

int exCRT(){
    for(int i=2;i<=n;++i){
        p1=p[i-1],p2=p[i],c1=c[i-1],c2=c[i];
        g=gcd(p1,p2);
        if((c2-c1)%g) return -1;
        p[i]=p1*p2/t;
        c[i]=inv(p1/t,p2/t)*((c2-c1)/t)%(p2/t)*p1+c1;
        c[i]=(c[i]%p[i]+p[i])%p[i];
    }
    return c[n];
}

然後寫一發bsgs~叫拔山蓋世的都是邪教!北上廣深才是王道!

map<LL,int> mmp;
LL BSGS(LL y,LL z,LL p){ mmp.clear();
    LL m=sqrt(p)+1; po=qpow(y,m,p);
    for(LL j=0;j<=m;++j){
        v[z]=j; z=z*y%c;
    } z=1;
    for(LL i=1;i<=m;++i){
        z=z*po%p;
        if(v[x]) return i*m-v[x];
    }
    return -1;
}

然後就是組合數題的boss了(僅代表個人觀點), 非常神的 擴充套件Lucas~~~~
去年這時候學的, 現在幾乎忘得一乾二淨
剛才打了一遍板子, 然而還是沒有背過...

LL mul(LL n,LL pi,LL pk){
    if(!n) return 1; LL ans=1;
    if(n/pk){
        for(LL i=2;i<=pk;++i)
            if(i%pi) ans=ans*i%pk;
        ans=qpow(ans,n/pk,pk);
    }
    for(LL i=2;i<=n%pk;++i)
        if(i%pi) ans=ans*i%pk;
    return ans*mul(n/pi,pi,pk)%pk;
}
LL C(LL n,LL m,LL p,LL pi,LL pk){
    if(m>n) return 0;
    LL a=mul(n,pi,pk),b=mul(m,pi,pk),c=mul(n-m,pi,pk),k=0,ans;
    for(LL i=n;i;i/=pi) k+=i/pi;
    for(LL i=m;i;i/=pi) k-=i/pi;
    for(LL i=n-m;i;i/=pi) k-=i/pi;
    ans=a*inv(b,pk)%pk*inv(c,pk)%pk*qpow(pi,k,pk)%pk;
    return ans*(p/pk)%p*inv(p/pk,pk)%p;
}
int main(){
    LL n,m,p,ans=0; scanf("%lld%lld%lld",&n,&m,&p); n+=m;
    for(LL x=p,i=2;i<=p;++i)
        if(x%i==0){
            LL pk=1;
            while(x%i==0) pk*=i,x/=i;
            ans=(ans+::C(n, m, p, i, pk))%p;
        }
    printf("%lld\n",ans);
}

再來補充一些反演相關:
最基本的列舉除法

for(int i=1,last;i<=n;i=last+1){
    last=n/(n/i);
    // 然後處理的是關於last和i-1的式子..(一般就是字首和相減..)
}

公式就記這幾個其他常見的都可以推...
\[ \mu*1=\epsilon \\ \varphi*1=n \\ id_k*1=\sigma_k \]

套路性的東西:
\[ \sum_{i=1}^n\sum_{j=1}^m[gcd(i,j)=1]=\sum_{d=1}^n\mu(d)\left\lfloor\frac nd\right\rfloor\left\lfloor\frac md\right\rfloor \\ \sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=1]=2*\sum_{d=1}^n\varphi(d)-1 \]

杜教篩的模板(舒老師說這玩意卵用沒有但我覺得很有用噠(肯定是我太弱了)):
\[ 要求出f(x)的字首和s_f(x), 我們要找到一個合適的函式g(x), 滿足g(x)和(f*g)(x)的字首和都易求(一般是要求O(1)). \\ 令s_{f*g}(x)表示(f*g)x的字首和, 那麼有: \\ s_f(x)=\frac{s_{f*g}(x)-\sum_{i=2}^ns_f(\left\lfloor\frac ni\right\rfloor)g(i)}{g(1)} (當然正常來說g(1)=1就不用除辣)\\ 然後我們列舉除法對上式整除分塊遞迴處理s_f(\left\lfloor\frac ni\right\rfloor)這個部分就可以了~ \]

於是附上幾個常見的易求字首和的函式的求和公式:
\[ \sum_{i=1}^n1(i)=n \\ \sum_{i=1}^n\epsilon(i)=1 \\ \sum_{i=1}^nid(i)=\frac{n(n+1)}2 \\ \sum_{i=1}^nid_2(i)=\frac{n(n+1)(2n+1)}6 \\ \sum_{i=1}^nid_3(i)=(\frac{n(n+1)}2)^2 \]

然後就是一些注意事項:

  • \(10^{10}\)範圍杜教篩篩\(O(n^\frac 23)\)大約是\(4.7*10^6\), 所以500W一般可以, 不過空間允許的話再篩大一點在1e7左右會更好~
  • \(10^{10}*{10^9+7}\)有機率會爆long long, 雖然情況比較少但我相信足夠毒瘤的出題人是可以有足夠的時間和耐心搞出來hack資料的..., 所以保險起見要開unsigned long long...
  • long long計算非常慢, 能用int的地方儘量用int, 當然那就要注意強轉long long步步取模(少一個都有可能炸←_←)的問題了~
  • 列舉因子的時候最好把設\(i=td\)寫出來防止把應該化的\(t\)丟掉(可以適當找下規律驗證化的式子對否)..
  • 大約就這樣吧, 說太多自己也是記不住的...