數論

GCD問題和LCM問題

兩個數的gcd

g=gcd(a,b) 則g<=a && g<=b

ll gcd(ll a,ll b)
{
    return b==0?a:gcd(b,a%b);
}

由該算法我們可以知道,給定a,b由a,b進行加減得出來的數都是gcd(a,b)的倍數,並且能得到任意倍數

兩個數的lcm

l=gcd(a,b) 則l>=a && l>=b

ll lcm(ll a, ll b)
{
    ll g=gcd(a,b);
    return a/g*b;
}

gcd循環(輾轉相除法的原理)

如果gcd(n,m)=g,則g=gcd(n,m)=gcd(n,m+i*n)=gcd(n+i*m,m)=gcd(1,g);其逆運算很關鍵,即我們可以用加法性質來簡化運算較大結果的部分。

質因數大於都m的數

UVa11440

一個數的所有質因數都大於m,這種條件一定要轉化到互質gcd=1纔好用結論處理,因此,我們認爲該數x和小於m的所有質數p互質,也即是gcd(x,p_{1}p_{2}..p_{n}=1),由於我們爲了方便尋找這樣的數,我們能想到互質定理(歐拉定理),由於小於等於m的數都是由以上質數組成,故gcd(x,m!)=1也是成立的。也即是我們可以利用歐拉定理求得小於等於m!的x的個數。再利用gcd循環求得n!中x的個數即可。

擴展歐幾里德

ax+by=gcd(x,y),已知a和b的情況下,求取gcd(a,b),x,y的值

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}

 

 

歐拉函數

定義:m=p_{1}^{e_{1}}p_{2}^{e_{2}}....p_{n}^{e_{n}}  其中p1,p2...pn都是質數,\varphi (m)=m*\prod (p_{i}-1)/p_{i},歐拉函數表示不超過m,且和m互質的數的個數。

//打出n的表 o(nlog(n))
int eular[maxn];
void get_eular()   //phi
{
    for(int i=1;i<maxn;i++)
        eular[i]=i;

    for(int i=2;i<maxn;i++)
        if(eular[i]==i)
            for(int j=i;j<maxn;j=j+i)
                eular[j]=eular[j]/i*(i-1);
}


//歐拉篩打表o(n)
int tot=0;
int phi[maxn],prime[maxn];
bool isPrime[maxn];
void getphi()
{
    for(int i=1;i<maxn;i++)isPrime[i]=true;
    phi[1]=1;
    for(int i=2;i<maxn;i++)
    {
        if(isPrime[i]==true)prime[++tot]=i,phi[i]=i-1;
        for(int j=1;j<=tot;j++)
        {
            if(i*prime[j]>=maxn)break;
            isPrime[i*prime[j]]=false;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

//直接獲得某個值的歐拉函數值 o(sqrt(n))
int eular(int temp)
{
    int result=temp;
    int sqrtt=sqrt(temp);
    for(int i=2;i<=sqrtt;i++)
    {
        if(temp%i==0)
        {
            result=result/i*(i-1);
            while(temp%i==0)      //剔除相同因數
                temp=temp/i;
        }
    }
    if(temp!=1)      //剩下一個質數
        result=result/temp*(temp-1);

    return result;
}

結論:

結論1:m是質數,則φ(m)=m-1,由文自定義容易獲得。

結論2:對於和m互質的數x,x^φ(m) mod m =1;費馬小定理的一般形式 x^(p-1) mod p=1 ( x<p,p爲質數)

故:當 b很大時 x^{b}=x^{b\bmod \varphi (m) } (\bmod m),可以將b縮小範圍

結論3:歐拉函數是積性函數,則對於任意互質的整數a和b有性質f(ab)=f(a)f(b)

結論4:當n爲奇數時,  。從定義容易看出,因爲n中沒有2的因子。

原根

x是奇素數p的原根,當且僅當{x^i %p | 1<=i<=p-1} 等於集合{ 1,...,p-1}。

結論:由於p是質數,他有Phi(p-1)個原根

求取a關於範圍b的互質個數

 UVA - 10214

由於歐拉函數只能求取a關於範圍1-a中和a互質的結果,因此,爲了求取範圍1-b中的結果,我們如下考慮gcd的循環:

將b分解成1-a      a+1-2*a      2a+1-3a...    na+1-b的部分,其中完整的部分有b/a個,最後一部分有a-b/a*a個結果,單獨運算即可

//a在範圍1-b上的互質的個數
int eular(int a,int b)
{
    int ans=b/a*eular[a];

    for(int i=1;i<=b%a;i++)
        if(gcd(a,i)==1)
            ans++;

    return ans;
}

Farey級數

F(n)(n>=2)表示有一系列不能約分的分數a/b(0<a<b<=n,且gcd(a,b)=1) 組成,求Fn。

由於gcd(a,b)=1,因此我們可以知道就是對於每個b,其中多少個a與之互質,也即是b的歐拉函數。

故Farey[i]=Farey[i-1]+eular[i];  Farey[2]=eular[2]=1;

圖像形式,也即是:看下半部分的,從原點射出一條線段,碰到的第一個點。加上(1,1)(0,1)(1,0)三個點,因爲gcd(x,y)=1

利用莫比烏斯反演可更快得出結果:大致如下g(1)=\sum_{i=1}^{n}mu[i]*(n/i)*(n/i)+2

 

質數問題

定義:除了1和本身以外不含有其他因數的數。也即是對於x來說i取(1,x-1),gcd(i,x)=1。

質數判定

二次探測:

如果x是一個質數,0<m<x,則方程m^2%x=1,只有m=1或m=(x-1)的解

Millar_rabin+二次探測 做法:

記x=(2^j)*m+1,則a^(x-1)%x ?=1,即變成了 檢測a^(2^j*m)%x是否等於1,也即是(a^2)^j * a^m %x ?= 1  

//0(sqrt(n))算法
bool isprime(ll x)
{
    ll sqrtx=sqrt(x);
    for(int i=2;i<=sqrtx;i++)
        if(x%i==0)
        return false;
    return true;
}


// Miller-Rabin測試
ll multi(ll a,ll b,ll n)   //大數乘法 a*b mod n  目標乘法變加法,極端消耗時間
{
    a=a%n;b=b%n;
    ll res=0;
    while(b)
    {
        if(b&1)
        {
            res=res+a;
            if(res>=n)res=res-n;
        }
        a=a<<1;
        if(a>=n)a=a-n;
        b=b>>1;
    }
    return res;
}
ll qpow(ll a,ll b,ll mod)   //超大數快速冪
{
    ll temp=1;
    while(b)
    {
        if(b&1)
            temp=multi(temp,a,mod);
        a=multi(a,a,mod);
        b=b>>1;
    }
    return temp;
}
inline ll random(ll n)
{
    return (ll)(rand()%(n-1)+1);
}
bool witness(ll a,ll n,ll x,int t)
{
    ll r=qpow(a,x,n);
    ll last=r;
    for(int i=1;i<=t;i++)
    {
        r=multi(r,r,n);
        if(r==1 && last!=1 && last!=n-1)return true;
        last=r;
    }
    if(r!=1)return true;
    return false;
}
bool Miller_rabin(ll n)
{
    if(n==2 || n==3 || n==5) return true;
    if((n<2) || (n&1==0) || (n%3==0) || (n%5==0))
        return false;

    ll x=n-1,t=0;
    while((x&1)==0)x=x>>1,t++;
    for(int k=0;k<10;k++)
        if(witness(random(n),n,x,t))   //如果rand的值過大
            return false;
    return true;
}

質數篩

// o(nloglog(n))
bool isPrime[maxn];
int prime[maxn];
int primeNum;

void getPrime()
{
    primeNum=0;
    for(int i=1;i<maxn;i++)
        isPrime[i]=true;

    for(int i=2;i<maxn;i++)
    {
        if(isPrime[i]==true)
        {
            prime[++primeNum]=i;
            for(ll j=(ll)i*(ll)i;j<maxn;j=j+i)
                isPrime[j]=false;
        }
    }
}

// 歐拉篩 O(n)    基於歐拉篩可以簡化很多篩法
int prime[maxn],tot=0;
bool isPrime[maxn];
void getPrime()
{
    memset(isPrime,true,sizeof(isPrime));
    for(int i=2;i<maxn;i++)
    {
        if(isPrime[i])prime[++tot]=i;
        for(int j=1;j<=tot;j++)
        {
            if(i*prime[j]>=maxn)break;
            isPrime[i*prime[j]]=false;
            if(i%prime[j]==0) break;
        }
    }
}

質數篩-函數法

 

質數分解

正常分解:

        int sqrtx=sqrt(x);
        for(int i=2;i<=sqrtx;i++)
        {
            while(x%i==0)
                x=x/i;
        }
        if(x!=1)
        {
            /**/
        }

先質數篩再分解,節約時間.按照選出來的數進行分解,對於一個數x,只需要篩選出sqrt(x)就好:

bool isPrime[maxn];
int prime[maxn];
int primeNum;

void getPrime()
{
    primeNum=0;
    for(int i=1;i<maxn;i++)isPrime[i]=true;

    for(int i=2;i<maxn;i++)
    {
        if(isPrime[i]==true)
        {
            prime[++primeNum]=i;
            for(ll j=(ll)i*(ll)i;j<maxn;j=j+i)
                isPrime[j]=false;
        }
    }
}

int main()
{
    int sqrtx=sqrt(x);
    for(int i=1;prime[i]<=sqrtx;i++)
    {
        int p=prime[i];
        while(x%p==0)x=x/p;
    }
    if(x!=1)
    {
        /**/
    }
}

Pollard_Rho分解

對於較大一點的數x來說,我們需要求他的sqrt(x)來進行因式分解也是一件比較麻煩的事情,因此我們考慮基於類似Miller_Rabbin的概率算法來處理相應問題。

處理思路是,先用Miller_Rabbin算法判定x是否是一個質數,如果是則進行Pollard分解

//如果太慢 記得把qpow 換成直接快速冪

ll multi(ll a,ll b,ll n)   //大數乘法 a*b mod n  目標乘法變加法,極端消耗時間
{
    a=a%n;b=b%n;
    ll res=0;
    while(b)
    {
        if(b&1)
        {
            res=res+a;
            if(res>=n)res=res-n;
        }
        a=a<<1;
        if(a>=n)a=a-n;
        b=b>>1;
    }
    return res;
}
ll qpow(ll a,ll b,ll mod)   //超大數快速冪
{
    ll temp=1;
    while(b)
    {
        if(b&1)
            temp=multi(temp,a,mod);
        a=multi(a,a,mod);
        b=b>>1;
    }
    return temp;
}
inline ll random(ll n)
{
    return (ll)(rand()%(n-1)+1);
}
bool witness(ll a,ll n,ll x,int t)
{
    ll r=qpow(a,x,n);
    ll last=r;
    for(int i=1;i<=t;i++)
    {
        r=multi(r,r,n);
        if(r==1 && last!=1 && last!=n-1)return true;
        last=r;
    }
    if(r!=1)return true;
    return false;
}
bool Miller_rabin(ll n)
{
    if(n==2 || n==3 || n==5) return true;
    if((n<2) || (n&1==0) || (n%3==0) || (n%5==0))
        return false;

    ll x=n-1,t=0;
    while((x&1)==0)x=x>>1,t++;
    for(int k=0;k<10;k++)
        if(witness(random(n),n,x,t))   //如果rand的值過大
            return false;
    return true;
}
/*      
ll gcd(ll a,ll b)   //分解出只有正數的快速方法
{
    if(a==0)return 1;
    if(a<0)return gcd(-a,b);
    while(b)
    {
        ll t=a%b;a=b;b=t;
    }
    return a;
}
*/
ll gcd(ll a, ll b)
{
    return b==0?a:gcd(b,a%b);
}
ll Pollard_rho(ll n,ll c)
{
    ll x=random(n);
    ll y=x,i=1,k=2;
    int num=0;
    while(true)
    {
        i++;
        x=(multi(x,x,n)+c)%n;
        ll d=gcd(y-x+n,n);  //由於用的是遞歸gcd,沒判斷正負,需要傳入正值+n會影響較多速度,gcd中處理較好
        if(d!=1 && d!=n)return d;
        if(y==x)return n;
        if(i==k)
        {
            y=x;
            k=k<<1;
        }
    }
}
void find_factor(ll n)
{           //遞歸進行質因數分解N
    if(Miller_rabin(n))  //尋找到質因數
    {
        minnum=min(minnum,n);       //尋找到一個質因數n,對其進行合適作操作
        return;
    }
    ll p=n;
    while (p>=n) p=Pollard_rho(p,random(n));
    find_factor(p);
    find_factor(n/p);
}

常見結論

1.在10^20的範圍類,兩個質數之間的差值不會超過1000;

 

 

 

MOD問題 

mod求和

UVa1363

給定n,k,求  \sum_{i=1}^{n} k\bmod i

我們將k mod i 進行分組,按照k/i=d的結果進行分組,我們即可看到規律。

當d=0時,k mod i=k.

當d=1時,i取(k/2+1,k)

當d=2時,i取(k/3+1,k/2)

當爲d時,i取(k/(d+1)+1,k/d)

當d>=sqrt(k)時,i取(k/(d+1)+1,k/d),即該範圍不多於1個值,可能無法取值。

故分三段取值d=0     d取(1,sqrt(k))      d>sqrt(k)   第一段直接得結論,第二段 用等差數列得結果,第三段枚舉

 

逆元問題

我們知道乘法和觸發本質是一樣的,因此在mod k 的前提下,我們需要求取 (a * b / c )mod k 的值,其實就是在求取((a mod k ) *(b mod k ) *( 1/c mod k ) )mod k 而 (1/c mod k)其實就是c在mod k下的逆元叫做inv(c)

//擴展歐幾里德求取a在m下的逆元
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}

如果k爲質數,我們可以使用費馬定理,利用快速冪處理

ll qpow(ll a,ll n,ll mod)
{
    ll temp=1;
    while(n)
    {
        if(n&1)temp=(temp*a)%mod;
        a=(a*a)%mod;
        n=n>>1;
    }
    return temp;
}
ll inv(ll a,ll m)
{
    return qpow(a,m-2,m);
}

逆元打表

ll inv[maxn];
void initInv()
{
    inv[0]=inv[1]=1;
    for(int i=2;i<mod+10;i++)inv[i]=(((-mod/i)*inv[mod%i])%mod+mod)%mod;
}

 

積性函數

定義:

積性函數:對於任意互質的整數a和b有性質f(ab)=f(a)f(b)的數論函數。

完全積性函數:對於任意整數a和b有性質f(ab)=f(a)f(b)的數論函數。

φ(n) -歐拉函數

μ(n) -莫比烏斯反演,關於非平方數的質因子數目

gcd(n,k) -最大公因子,當k固定的情況

d(n) -n的正因子數目

σ(n) -n的所有正因子之和

莫比烏斯反演

//單獨求解
int mu(int x)
{
    int cnt=1;
    int sq=sqrt(x);
    for(int i=2;i<=x&&i<=sq;i++)
    {
        if(x%i!=0)continue;
        x=x/i;
        if(x%i==0)return 0;
        cnt=-cnt;
    }
    if(x!=1)cnt=-cnt;
    return cnt;
}


//O(n)
int mu[maxn];
bool vis[maxn];
int prime[maxn],tot;
void getMu()
{
    memset(vis,0,sizeof(vis));
    mu[1]=1;
    tot=0;
    for(int i=2;i<maxn;i++)
    {
        if(vis[i]==false)
        {
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<tot;j++)
        {
            if(i*prime[j]>=maxn)break;
            int tmp=i*prime[j];
            vis[tmp]=true;
            if(i%prime[j]==0)
            {
                mu[tmp]=0;
                break;
            }
            else mu[tmp]=-mu[i];
        }
    }
}

多項式問題

差分

給定多項式f(n)=a_{n}n^{b{n}}+a_{n-1}n^{b_{n-1}}+...+a_{0}, 當n可以取1.....n等值,使得計算出來的結果滿足一定的條件,由於在多項式中的係數an可能比較大,使得結果不方便計算,或者由於n的取值比較大使得我們的for循環模擬比較大,我們不妨考慮差分的思想,想一想f(n)-f(n-1)滿足什麼樣的結論。

結論

結論1:f(n)=c_{n-1}f(n-1)+c_{n-2}f(n-2)+....c_{n-k-1}f(n-k-1) + (k=bn)   可從方程觀查看未知數個數判斷出結論.

例:uva 1069

給出形如 (P(n))/D 的式子,求出這個式子關於正n取值整數是否全是整數.

由於n的取值很大,我們可以考慮從差分入手,每個值都能被D整除,則他們的差分也能被D整除,則差分的差分也能被D整除,故我們能夠得出一個結論:當n取0-bn時,對於每一個f(n)/D結果都是整數,我們就可以得到一個結論對於n去任意正整數f(n)/D爲整數。

 

 

組合數問題

組合數公式: c(p,q)=p! / ((p-q)! * q!)

組合數除法

計算C(p, q) / C(s, r)    uva 10375

利用組合數公式進行運算 算則儘量少的運算次數,即q>p/2時,p=p-q

運算時乘大數除大數  乘小數除小數 防止精度越界。

int p,q,r,s;
int main()
{
    while(scanf("%d %d %d %d",&p,&q,&r,&s)!=EOF)
    {
        if(q>p/2)q=p-q;
        if(s>r/2)s=r-s;
        double ans=1.00;
        if(q>s)
        {
            for(int i=1;i<=s;i++)
                ans=ans*(p+1-i)/(r+1-i);
            for(int i=s+1;i<=q;i++)
                ans=ans*(p+1-i)/i;
        }
        else
        {
            for(int i=1;i<=q;i++)
                ans=ans*(p+1-i)/(r+1-i);
            for(int i=q+1;i<=s;i++)
                ans=ans/(r+1-i)*i;
        }
        printf("%.5f\n",ans);
    }
    return 0;
}

組合數取模

組合數取模也即是C(m,n)%p,其中p爲質數,怎麼求,有公式可知:C(m,n)\%p=C(m/p,n/p)*C(m\%p,n\%p)\%p.

該定理又叫lucas定理。

這裏寫圖片描述

故代碼如下:(p較小,且p爲質數)

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
inline ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll C(ll n,ll m,ll p)
{
    if(m>n)return 0;
    ll up=1,down=1;
    for(int i=n-m+1;i<=n;i++)up=up*i%p;
    for(int i=1;i<=m;i++)down=down*i%p;
    return up*inv(down,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
    if(m==0)return 1;
    return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}

當p不爲質數時,我們如何處理呢?

我們知道對p進行分解可以得到p=p1^a1 * p2 ^a2 *.....*pn^an, 且每一項之間兩兩互質;故如果求得X%(p1^a1)=b1,X%(p2^a2)=b2,.......,X%(pn^an)=bn,我們是不是可以用CRT(因爲各項互質)將上式合併得到關於X%p=b的結果b;

假如ai都爲1,即p爲square-free-number,則求解方式直接採用質數的盧卡斯定理即可.

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
inline ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll C(ll n,ll m,ll p)
{
    if(m>n)return 0;
    ll up=1,down=1;
    for(int i=n-m+1;i<=n;i++)up=up*i%p;
    for(int i=1;i<=m;i++)down=down*i%p;
    return up*inv(down,p)%p;
}
ll Lucas(ll n,ll m,ll p)
{
    if(m==0)return 1;
    return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}

ll multi(ll a,ll b,ll n)
{
    a=(a%n+n)%n;b=(b%n+n)%n;
    ll res=0;
    while(b)
    {
        if(b&1)
        {
            res=res+a;
            if(res>=n)res=res-n;
        }
        a=a<<1;b=b>>1;
        if(a>=n)a=a-n;
    }
    return res;
}
ll crt(ll *a,ll *m,int num)
{
    ll lcm=1;
    for(int i=0;i<num;i++)lcm=lcm*a[i];
    ll ans=0;
    for(int i=0;i<num;i++)
       ans=ans+multi(multi(inv(lcm/a[i],a[i]),lcm/a[i],lcm),m[i],lcm);
    return (ans+lcm)%lcm;
}

int num;
ll a[20],b[20];
ll exLucas(ll n,ll m,ll p)
{
    for(int i=0;i<p;i++)
    {
        scanf("%lld",&a[i]);
        b[i]=Lucas(n,m,a[i]);
    }
    ll ans=crt(a,b,p);
    return ans;
}

如果p不是square-free-number,CRT部分只需各數互質,不會出現問題,但是lucas定理部分由於pi^ai不是質數,需要進行改動:

計算C(n,m)%p^t。我們知道,C(n,m)=n!/m!/(n-m)!,若我們可以計算出n!%p^t,我們就能計算出m!%p^t以及(n-m)!%p^t。我們不妨設x=n!%p^t,y=m!%p^t,z=(n-m)!%p^t,那麼答案就是x*reverse(y,p^t)*reverse(z,p^t)(reverse(a,b)計算a對b的乘法逆元)。那麼下面問題就轉化成如何計算n!%p^t。比如p=3,t=2,n=19

n!=1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19n!=1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19  
=(1∗2∗4∗5∗7∗8∗10∗11∗13∗14∗16∗17∗19)∗36∗(1∗2∗3∗4∗5∗6)=(1∗2∗4∗5∗7∗8∗10∗11∗13∗14∗16∗17∗19)∗36∗(1∗2∗3∗4∗5∗6) 
根據這個例子發現,求解n!可以分爲3部分:第一部分是pipi的冪的部分,也就是3636即pi⌊npi⌋pi⌊npi⌋,可以直接求解;第二部分是一個新的階乘,也就是6!即⌊npi⌋!⌊npi⌋!,可以遞歸下去求解;第三部分是除前兩部分之外剩下的數 
考慮第三部分如何求解 
發現第三部分在模pikipiki意義下是以pikipiki爲週期的,即(1∗2∗4∗5∗7∗8)≡(10∗11∗13∗14∗16∗17)(modpiki)(1∗2∗4∗5∗7∗8)≡(10∗11∗13∗14∗16∗17)(modpiki),所以只求pikipiki長度的即可;但是還剩下一個孤立的19,可以發現剩下孤立的數長度不會超過pikipiki,只需要暴力求解即可 
e.最後一個問題是對於求出的m!%pikim!%piki和(n−m)!%piki(n−m)!%piki有可能與pikipiki不互質,無法求逆元 
所以要將m!%pikim!%piki和(n−m)!%piki(n−m)!%piki中質因子pipi先全部除去,求出逆元后再全部乘回去 
計算n!中質因子p的個數x的公式爲x=⌊np⌋+⌊np2⌋+⌊np3⌋+...x=⌊np⌋+⌊np2⌋+⌊np3⌋+... 
遞推式也可以寫爲f(n)=f(⌊np⌋)+⌊np⌋

ll qpow(ll a,ll b,ll m)
{
    ll tmp=1;
    while(b)
    {
        if(b&1)tmp=tmp*a%m;
        a=a*a%m;
        b=b>>1;
    }
    return tmp;
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll fac(ll n, ll p, ll pk)
{
    if(!n)return 1;
    ll res=1;
    if(n/pk)
    {
        for(ll i=2;i<=pk;i++)if(i%p)res=res*i%pk;
        res=qpow(res,n/pk,pk);
    }
    for(ll i=2;i<=n%pk;i++)if(i%p)res=res*i%pk;
    return res*fac(n/p,p,pk)%pk;
}
ll Lucas(ll n,ll m,ll pi,ll pk)
{
    if(m>n)return 0;
    ll a=fac(n,pi,pk),b=fac(m,pi,pk),c=fac(n-m,pi,pk),k=0;
    for(ll i=n;i!=0;i=i/pi)k=k+i/pi;
    for(ll i=m;i!=0;i=i/pi)k=k-i/pi;
    for(ll i=n-m;i!=0;i=i/pi)k=k-i/pi;
    return a*inv(b,pk)%pk*inv(c,pk)%pk*qpow(pi,k,pk)%pk;
}
ll multi(ll a,ll b,ll n)
{
    a=(a%n+n)%n;b=(b%n+n)%n;
    ll res=0;
    while(b)
    {
        if(b&1)
        {
            res=res+a;
            if(res>=n)res=res-n;
        }
        a=a<<1,b=b>>1;
        if(a>=n)a=a-n;
    }
    return res;
}
ll crt(ll *a,ll *m,int num)
{
    ll lcm=1;
    for(int i=0;i<num;i++)lcm=lcm*a[i];
    ll ans=0;
    for(int i=0;i<num;i++)
       ans=ans+multi(multi(inv(lcm/a[i],a[i]),lcm/a[i],lcm),m[i],lcm);
    return (ans+lcm)%lcm;
}

int tot;
ll a[20],b[20],px[maxn];
void divide(ll x)
{
    tot=0;
    ll sq=(ll)sqrt(x*1.0+2.0);
    for(int i=2;i<=sq&&x!=1;i++)
    {
        if(x%i==0)
        {
            px[tot]=i,a[tot]=1;
            while(x%i==0)a[tot]=a[tot]*i,x=x/i;
            tot++;
        }
    }
    if(x!=1)px[tot]=a[tot++]=x;
}
ll exLucas(ll n,ll m,ll p)
{
    divide(p);
    for(int i=0;i<tot;i++)b[i]=Lucas(n,m,px[i],a[i]);
    ll ans=crt(a,b,tot);
    return ans;
}

直角三角形

1.畢達哥拉斯定理

所有形如a^{2}+b^{2}=c^{2}的數,有且僅有一下原型數:

x=m^{2}-n^{2},y=2mn,z=m^{2}+n^{2},其中m>n,gcd(m,n)=1,m (mod 2)!=n(mod2)

暴力搜索得:

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}

int maxs=sqrt(l);
for(int i=1;i<=maxs;i++)
{
    int nn=i*i;
    for(int j=i+1;j<=maxs;j++)
    {
        int mm=j*j;
        if()
        {
            //超過,退出條件
        }
        if(i%2!=j%2 && gcd(i,j)==1)
        {
            int x=mm-nn;
            int y=2*i*j;
            int z=mm+nn;
            /*****/
            num=num++;
        }
    }
}

佩爾方程

佩爾方程:即形如x^{2}-dy^{2}=1 形式的方程;

佩爾方程的解法:即先計算出最小解:x=sqrt(1+dy^{2}) y=1.2.3..,其他所有的解可以用遞推形式寫出來

\binom{x_{n}}{y_{n}}=\binom{x_{n-1}\quad dy_{n-1}}{y_{n-1}\quad x_{n-1}} \binom{x_{1}}{y_{1}} ,寫成冪的形式,也即是

\binom{x_{n}}{y_{n}}=\binom{x_{1}\quad dy_{1}}{y_{1}\quad x_{1}}^{n-1} \binom{x_{1}}{y_{1}}

int n,k;
int main()
{
    while(cin>>n>>k)
    {
        int x=0,y;           //尋找特解
        for(y=1;y<5000;y++)
        {
            x=sqrt(1+n*y*y);
            if(x*x-n*y*y==1)
                break;
        }
        if(y==5000)
        {
            //no answer
        }
                //快速冪運算  計算第k個解
        Mat e;
        e.m[1][1]=x;e.m[1][2]=n*y;
        e.m[2][1]=y;e.m[2][2]=x;
        e.row=2,e.col=2;
        Mat res=mat_pow(e,k-1);
        int ans=(res.m[1][1]*x+res.m[1][2]*y)%mod;
        printf("%d\n",ans);
    }
    return 0;
}

中國剩餘定理

給出幾個線性同餘方程:求最小的總的個數

一元線性同餘方程組

其中m1,m2,mn相互互質;

容易知道,當求得一個結果xtemp的時候,令lcm=m1*m2*..*mn,則結果爲x=xtemp+k*lcm。

其中最小的那個值爲 x=(xtemp%lcm+lcm)%lcm;

引論:對於一個式子 x%a=m,我們很容易寫出 x=(a*k+1)*m,本質上即找到mod a爲1的數,使其乘餘數m即可。

 

對於多個式子,本質上是這樣多個式子的和,即xtemp=(a1*k1+1)*m1+...+(an*kn+1)*mn 

不妨記爲:xtemp=x1*m1+...+xn*mn; 

其中xi表示mod ai爲1的數,且爲ki*π(aj)(j!=i),,即xi=ki*(lcm/ai).

故可以得到方程: ki*(lcm/ai)=1(mod ai)

故可以的得到式子:(lcm/ai)*ki+ai*y=1

即可以用exgcd求出ki的值從而得到xi的值,最終得到xtemp的結果。

 

代碼過程:按照上述結果,先求出lcm,再根據每一個a[i],求出(lcm/ai)*ki+ai*y=1中ki的值,最後求取ki*(lcm/ai)*mi的和

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
inline ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll crt(ll *a,ll *m,int num)
{
    ll lcm=1;
    for(int i=0;i<num;i++)lcm=lcm*a[i];
    ll ans=0;
    for(int i=0;i<num;i++)ans=(ans+inv(lcm/a[i],a[i])*(lcm/a[i])*m[i])%lcm; //溢出取模
    return (ans+lcm)%lcm;
}

 

擴展中國剩餘定理

擴展中國剩餘定理即a1,a2,a3之間不一定互質的情況出現。

這時候需要對所給出的式子進行兩個兩個的組合,

考慮一下式子:

x ≡ m1 mod a1

x ≡m2 mod a2

所以有x= a1*k1+m1 = a2*k2+m2

即  a1*k1 = a2*k2+m2-m1

由於a1與a2不一定互質,故令d=gcd(a1,a2)。 要求(m2-m1)%d=0;如果不爲0,說明無法得到結果。

上式可以化成(a1/d)*k1=(m2-m1)/d+a2/d*k2;

即 (a1/d)*k1=c(mod (a2/d)); 因爲c不一定是1,故求出a1/d關於a2/d的逆元,也即是 k1=c*inv(a1/d) (mod a2/d)

使用一次exgcd可以將k1temp的結果計算出來。

因爲k1每變化a2/d的值,k2就會變化a1/d的值,則k1=k1temp+(a2/d)*y.

所以x=a1*(k1temp+(a2/d)*y )+m1; 展開可得 x=(a1*k1temp+m1) +(a1*a2/d)*y;

也即是x=(a1*k1temp+m1) (mod (a1*a2/d));

也就是說,找到了這麼一個數,他滿足12等式計算出來的結果,並且每(a1*a2/d)的差都滿足該等式。

合併到最後得到x=m (mod a);也就是x=k*a+m;

 

代碼過程:

提前定義一個大A和大M,表示x=M(mod A);

對於每一組a1,a2,求出其gcd值d,再代入exgcd(a1/d,a2/d,x,y),求得的x即爲a1/d在a2/d下的逆元,乘以c也即是k1的值。

就可以得到x=(a1*k1temp+m1) (mod (a1*a2/d));

最後x=(m%a+a)%a;

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)x=1,y=0;
    else d=exgcd(b,a%b,y,x),y=y-(a/b)*x;
    return d;
}
ll excrt(ll *a,ll *m,int num)
{
    ll A=a[0],M=m[0];
    for(int i=1;i<num;i++)
    {
        ll x,y;
        ll d=exgcd(A,a[i],x,y);    //求兩個式子a的gcd值,同時求出inv(a1/d)(mod a2/d)的結果

        ll c=m[i]-M;    //判斷兩個式子組成的結果是否正確
        if(c%d)return -1;
        c=c/d,A=A/d,a[i]=a[i]/d;
        ll k=((x*c)%a[i]+a[i])%a[i];//計算c*inv(a1/d)(mod a2/d),求出k值並防止x過大或者過小
        M=M+A*d*k;      //獲得新式子的M
        A=A*a[i]*d;     //獲得新式子的A
        M=M%A;          //防止M過大過小
    }
    ll ans=M>0?M:M+A;    //當所求數大於0採用。
    //ll ans=(M%A+A)%A;  //M=A的情況哦,可以爲0次時使用!!!! 不是輸出0
    return ans;
}

 

BSGS

BSGS算法即處理a^x≡b(mod p) (p爲質數)的算法。

已知a,b,p的情況,求對應滿足成立式子的最小x。

 由費馬定理可以知道,a^1 %p...a^(p-1)%p會遍歷正好一遍0-p-1 因此x必定在0-p-1之間。

由此創建了bsgs算法,令x=m*i-t  其中m是個定值m=sqrt(p+0.5);即利用平方分割的思想進行處理,在平方分割的每個桶中,遍歷尋找結果。

a^(m*i-t)≡b(mod p)   等價於 ((a^m)^i)≡b*a^t (mod p), 令c=a^m ,得c^i≡b*a^t (mod p)由此我們可以將每一個t對應的[1,m]對應的值存入map中,需要的t值越大越好,因爲用的減法,故每次計算出的值,直接map就好,不用判斷。

    ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
    hashmap.clear();   //map 放裏面不用每次清空 浪費空間,節約時間
    ll a_t=b;  //利用連乘求值
    for(int t=1;t<=m;t++)
    {
        a_t=(a_t*a)%p;
        hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射,用結果映射t t爲1的時候計算結果爲b*a mmap[b*a]=t;
    }

讓i從[0,m]遍歷,去尋找對應的值是否存在,第一個找到的值對應的i,t就是我們需要的值。

typedef long long ll;

ll qpow(ll a,ll b,ll n) //快速冪,用於求a^m 以及求逆元
{
    ll temp=1;
    while(b)
    {
        if(b&1)temp=(temp*a)%n;
        a=(a*a)%n;
        b=b>>1;
    }
    return temp;
}

ll bsgs(ll a,ll b,ll p)
{
    ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
    hashmap.clear();   //map 放裏面不用每次清空 浪費空間,節約時間
    ll a_t=b;  //利用連乘求值
    for(int t=1;t<=m;t++)
    {
        a_t=(a_t*a)%p;
        hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射
    }
    ll c=qpow(a,m,p);     //獲取c
    ll c_i=1;
    for(int i=1;i<=m;i++)   //計算
    {
        c_i=(c_i*c)%p;
        ll num=hashmap.get(c_i);
        if(num!=-1)return m*i-num;
    }
    return -1;
}

int main()
{
    ll p,b,n;
    while(scanf("%lld %lld %lld",&p,&b,&n)!=EOF)
    {
        ll result=bsgs(b,n,p);

        if(result==-1)
            printf("no solution\n");
        else
            printf("%lld\n",result);
    }
    return 0;
}

 

擴展BSGS

擴展BSGS即在取mod的時候,p不一定是質數。

優先判斷,當b==1的時候,即a^0==1 返回0;

即a^x≡b(mod p)  可以寫成 a^x=kp+b, 由於a和p不互質,故存在d=gcd(a,p);使得a/d * a^(x-1)=k*(p/d)+b/d;成立,因爲三個部分都是整數,故b/d一定是整數。如果不是,說明不存在這樣的式子可以成立。

記tmp=a/d,p=p/d,b=b/d 故上式變成了 a/d * a^(x-1)=k*p+b;   即tmp*a^(x-1)=b(mod p),同理繼續上述操作,直到d=gcd(a,p)=0;

有tmp=a/d1 * a/d2 *......*a/dk             p=p/d1/d2../dk   b=b/d1/d2.../dk    a^(x-k)

故上式變成 tmp*a^(x-k)=b(mod p);  

在這個計算過程中如果發現了某一個tmp==b 說明了a^(x-k)==1,故直接返回k即可。

    ll tmp=1,k=0;   //tmp被分割出來的部分的值 k進行了多少次gcd分割
    while(true)
    {
        ll d=gcd(a,p);
        if(d==1)break;
        if(b%d) return -1;
        k++;
        b=b/d;p=p/d;
        tmp=(a/d*tmp)%p;
        if(tmp==b)return k;
    }

處理完後,即可發現上式變成了a^(x-k)=b/tmp(mod p);

這裏需要處理b/tmp(mod p),由於先需要處理b/tmp的最小公倍數,再將化簡後的結果用exgce求取tmp關於p的逆元,因爲p不一定是質數,故不能用費馬定理求逆元。用exgcd來求逆元,

    b=(b*inv(tmp,p))%p;

用一次常規bsgs即可

    ll result=bsgs(a,b,p);
    if(result==-1)return -1;
    else return result+k;

總的代碼

struct HashNode
{
    ll key;
    ll val;
    int next;
};
const int hashsize=31643,maxnode=100000;
struct Hash
{
    HashNode node[maxnode];
    int head[maxnode];
    int tot;
    void clear()
    {
        memset(head,-1,sizeof(head));
        tot=0;
    }
    void add(ll key,ll val)
    {
        ll x=key%hashsize;
        node[tot].key=key;
        node[tot].val=val;
        node[tot].next=head[x];
        head[x]=tot++;
    }
    ll get(ll key)
    {
        ll x=key%hashsize;
        for(int i=head[x];i!=-1;i=node[i].next)
            if(node[i].key==key) return node[i].val;
        return -1;
    }
};
Hash hashmap;

ll qpow(ll a,ll b,ll n) //快速冪,用於求a^m 以及求逆元
{
    ll temp=1;
    while(b)
    {
        if(b&1)temp=(temp*a)%n;
        a=(a*a)%n;
        b=b>>1;
    }
    return temp;
}
//擴展歐幾里德求取a在m下的逆元
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    ll d=a;
    if(b==0)
    {
        x=1;y=0;
    }
    else
    {
        d=exgcd(b,a%b,y,x);
        y=y-(a/b)*x;
    }
    return d;
}
ll inv(ll a,ll m)
{
    ll x,y;
    exgcd(a,m,x,y);
    return (x%m+m)%m;
}
ll bsgs(ll a,ll b,ll p)
{
    ll m=ceil(sqrt(p+0.5)); //獲取n的平方根
    hashmap.clear();   //map 放裏面不用每次清空 浪費空間,節約時間
    ll a_t=b;  //利用連乘求值
    for(int t=1;t<=m;t++)
    {
        a_t=(a_t*a)%p;
        hashmap.add(a_t,t); //計算b*a^t(mod p) 反向映射,用結果映射t t爲1的時候計算結果爲b*a mmap[b*a]=t;
    }
    ll c=qpow(a,m,p);     //獲取c
    ll c_i=1;
    for(int i=1;i<=m;i++)   //計算
    {
        c_i=(c_i*c)%p;
        ll num=hashmap.get(c_i);
        if(num!=-1)return m*i-num;
    }
    return -1;
}
ll gcd(ll a ,ll b)
{
    return (b==0?a:gcd(b,a%b));
}
ll exbsgs(ll a,ll b,ll p)
{
    if(b==1)return 0; //特殊情況先考慮
    ll tmp=1,k=0;   //tmp被分割出來的部分的值 k進行了多少次gcd分割
    while(true)
    {
        ll d=gcd(a,p);
        if(d==1)break;
        if(b%d) return -1;
        k++;
        b=b/d;p=p/d;
        tmp=(a/d*tmp)%p;
        if(tmp==b)return k;
    }
    b=(b*inv(tmp,p))%p;
    ll result=bsgs(a,b,p);
    if(result==-1)return -1;
    else return result+k;
}
//  538 23454 14704
int main()
{
    ll k,p,n;
    while(scanf("%lld %lld %lld",&k,&p,&n)!=EOF)
    {
        if(n>p)
        {
            puts("Orz,I can’t find D!");
            continue;
        }
        ll result=exbsgs(k,n%p,p);
        if(result==-1)printf("Orz,I can’t find D!\n");
        else printf("%lld\n",result);
    }
    return 0;
}

伯努利數

給定k,計算i的k次方求和轉換爲多項式求和的係數

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章