0x30 數學

0x31 質數

定義

若一個數,只有一和它自己兩個因子,那麼這個數就是一個質數c++

在天然數集中,小於\(n\)的質數約有\(ln(n)\)es6

試除法

試除法是經常使用判斷素數的方法算法

inline bool is_prime( int x )
{
    if( x < 2 ) return 0;
    for( register int i = 2 ; i * i <= x ; i ++ )
    {
        if( x % i == 0 ) return 0;
    }
    return 1;
}

素數的篩選

Eratosthenes 篩法 (埃拉托色尼篩法)

每次只用素數去篩複雜度\(O(nlog_{log_{n}})\)數組

const int N = 1005;
int n,prime[N];

inline void primes()
{
    for(register int i = 2;i <= n;i ++)
    {
        if(prime[i]) continue;
        for(register int j = 2; i * j <= n; j++) prime[i*j] = 1;
    }
    return ;
}

線性篩 (歐拉篩法)

每次只用一個數用小於當前這個數最小質因子的質數去篩其餘數,即保證每一個數都被本身的最小質因子篩掉ide

const int N = 10005;
int n,prime[N],cnt;
bool vis[N];

inline void primes()
{
    for(register int i = 2;i <= n;i ++)
    {
        if(!vis[i]) prime[++cnt] = i;
        for(register int j = 1;j <= cnt && i * prime[j] <= n; j ++)
        {
            vis[i * prime[j]] = 1;
            if(i % prime[j] == 0) break;
        }
    }
}

質因數分解

算數基本定理

任何一個大於1的數均可以被分解成有限個質數乘積的形式函數

試除法

相似埃式篩,咱們直接枚舉影子而後把當前因子所有除盡便可優化

分解成 \(p_{1}\times _{2}\times p_{3}\times \cdots \ p_{n}\)這種形式ui

const int N = 1005;
int p[N];

inline int factorize(int x)
{
    register int cnt = 0;
    for(register int i = 2;i * i <= x;i ++)
    {
        while(x % i == 0)
        {
            p[++cnt] = i;
            x /= i; 
        }
    }
    if(x > 1) p[++cnt] = x;
    return cnt;
}

分解成 \(p_{1}^{k_{1}} \times p_{2}^{k_{2}} \times p_{3}^{k_{3}} \times \cdots \ p_{n}^{k_{n}}\)es5

const int N = 1005;
int p[N],power[N];


inline int factorize(int x)
{
    register int cnt = 0;
    for(register int i = 2;i *i <= x;i ++)
    {
        if(x%i) continue;
        p[++cnt] = i;
        while(x%i == 0) x /= i,power[cnt] ++;
    }
    if(x == 1) goto end;
    p[++cnt] = x;
    power[cnt] = 1;
    end : return cnt
}

AcWing 196. 質數距離

這道題數據的範圍很是的大,咱們沒有辦法在一秒內求出全部質數spa

可是咱們知道一個合數\(x\)在必定是一個小與\(\sqrt{x}\)的質數的倍數

因此咱們能夠求出\((1\cdots \sqrt{U})\)的全部質數,而後對於每一個區間作埃式篩

而後暴力遍歷一遍區間便可

#include <bits/stdc++.h>
#define LL long long

using namespace std;

const int N = 1000010;

int prime[N], cnt , p[N] , tot;
bitset< N > v;

inline void primes()
{
    int n = 50000;
    for( register int i = 2 ; i <= n ; i ++ )
    {
        if( !v[i] ) prime[ ++ cnt ] = i;
        for( register int j = 1 ; j <= cnt && i * prime[j] <= n ; j ++ )
        {
            v[ i * prime[j] ] = 1;
            if( i % prime[j] == 0 ) break;
        }
    }
}


int main()
{
    long long l, r;
    primes();
    while (cin >> l >> r)
    {
        v.reset();
        for (int i = 1; i <= cnt; i ++ )
        {
            int t = prime[i];
            // 把[l, r]中全部t的倍數篩掉
            for (long long j = max((l + t - 1) / t * t, 2ll * t); j <= r; j += t)
                v[j - l] = true;
        }

        tot = 0;
        for (int i = 0; i <= r - l; i ++ )
            if (!v[i] && i + l > 1)
                p[tot ++ ] = i + l;

        if (tot < 2) puts("There are no adjacent primes.");
        else
        {
            int minp = 0, maxp = 0;
            for (int i = 0; i + 1 < tot; i ++ )
            {
                int d = p[i + 1] - p[i];
                if (d < p[minp + 1] - p[minp]) minp = i;
                if (d > p[maxp + 1] - p[maxp]) maxp = i;
            }

            printf("%d,%d are closest, %d,%d are most distant.\n", p[minp], p[minp + 1], p[maxp], p[maxp + 1]);
        }
    }
    return 0;
}

AcWing 197. 階乘分解

\(N!\)中質因子\(p\)的個數等於\(1\cdots N\)中每一個數的質因子\(p\)的個數之和

包含一個質因子p的數顯然都是\(p​\)的倍數,因此有\(\left \lfloor \frac{N}{p}\right \rfloor​\)個質因子

同理包含第二個(不是包含兩個)質因子\(p\)的數顯然都是\(p^2\)的倍數,因此有因此有\(\left \lfloor \frac{N}{p^2}\right \rfloor\)個質因子,注意不是\(2\times\left \lfloor \frac{N}{p^2}\right \rfloor\)個,由於第一個已經統計過了

因此\(N!\)中一共包含\(\sum_{k=1}^{p^k\le N}\lfloor\frac{N}{p^k}\rfloor\)個質因子

因此咱們先求出全部的質因子,再用以上方法求出全部的質因子數便可

#include <bits/stdc++.h>
using namespace std;


const int N = 1000010;
int n , prime[N] , tot;
bool v[N];

inline void primes()
{
    for( register int i = 2 ; i <= n ; i ++ )
    {
        if( !v[i] ) prime[ ++ tot ] = i;
        for( register int j = 1 ; j <= tot && i * prime[j] <= n ; j ++ )
        {
            v[ i * prime[j] ] = 1;
            if( i % prime[j] == 0) break;
        }
    }
    return ;
}


int main()
{
    cin >> n;
    primes();

    for (int i = 1; i <= tot; i ++ )
    {
        register int p = prime[i] , cnt = 0;
        for ( p ; p <= n ; p *= prime[i] )
        {
            cnt += n / p;
            if( p > n / prime[i] ) break;
            //等價於 if( p * prime[i] > n ) break; 防止溢出
        }
        cout << prime[i] << ' ' << cnt << endl;
    }

    return 0;
}

0x32 約數

定義

若整數\(n\)除以整數\(x\)的餘數爲\(0\),即\(d\)能整除\(n\),則稱\(d\)\(n\)的約數,\(n\)\(d\)的倍數,記爲\(d|n\)

算術基本定理推論

由算數基本定理得正整數N能夠寫做\(N=p_1^{C_1}\times p_2^{C_2} \times p_3^{C_3} \cdots \times p_m^{C_m}\)

N的正約數個數爲(\(\Pi\)是連乘積的符號,相似\(\sum\))

\[ (c_1+1)\times (c_2+1)\times \cdots (c_m+1)=\Pi_{i=1}^{m}(ci+1) \]

\(N\)的全部正約數和爲
\[ (1+p_1+p_1^2+\cdots +p_1^{c_1})\times\cdots\times(1+p_m+p_m^2+\cdots +p_m^{c_m})=\prod_{i=1}^{m}(\sum_{j=0}^{c_i}(p_i)^j) \]

\(N\)的正約數的集合

對於任意的\(d|n\)\((\frac{n}{d})|n\)

因此只要掃描\(1\cdots\sqrt n\)就能找到n的全部正約數,複雜度\(O(\sqrt n)\)

int factor[N] , tot = 0;
for( int i = 1 ; i * i <= n ; i ++ )
{
    if( n % i ) continue;
    factor[ ++ tot] = i;
    if( i != n / i ) factor[ ++ tot ] = n / i;
}

AcWing 198. 反素數

引理1

\(1\cdots N\)中最大的反素數就是約數個數最多的數中最小的一個

證實:

\(m\)\(1\cdots N\)中約數個數最多的數中最小的一個。根據\(m\)的定義,\(m\)知足

  1. $\forall x < m ,g(x)\le g(m) $
  2. \(\forall x>m,g(x) \le m\)

第一條性質說明\(m\)是反素數,第二條性質說明大於\(m\)的都不是反素數

引理2

\(1\cdots N\)中任何數的不一樣質因子都不超過\(10\)個,任何質因子的指數總和不超過\(30\)

證實:

最小的\(11\)個質因子乘積\(2\times3\times5\times7\times11\times13\times17\times19\times23\times29\times31>2\times10^9\)

最小的質數的\(31\)次方\(2^{31}>2\times10^9\)

引理3

\(\forall x \in [1,N]\),x爲反素數的必要條件是:

x分解質因數後可寫做\(2^{c_1}\times3^{c_3}\times5^{c_3}\times7^{c_4}\times11^{c_5}\times13^{c_6}\times17^{c_7}\times19^{c_8}\times23^{c_9}\times29^{c_{10}}\)

而且$c_{1}\geq c_{2}\geq c_{3}\geq c_{4}\geq c_{5}\geq c_{6}\geq c_{7}\geq c_{8}\geq c_{9}\geq c_{10}\geq0 $

證實:

反正法,由引理\(2\)\(x\)的質因數分解式中存在一項\(p^k(p>29)\),則一定有一個不超過29的質因子\({p}’\)不能整除\(x\)。根據算數基本定理的推論,\(\frac{x}{p^k}\times{p}'^k\)的約數個數與\(x\)的約數個數相等,但前者更小,因此,這與反質數的定義矛盾。故\(x\)只包含\(29\)之內的質因子

同理,若\(x\)的質因子不是連續若干最小的,或者質數不單調遞減,咱們能夠經過上述交換質因子的方法,的到一個比\(x\)更小的、但約數個數相同的整數。所以假設不成立,原命題成立

綜上所述,咱們能夠用\(DFS\)肯定前十個質數的指數,並知足指數單調遞減,總乘積不超過N,同時記錄約數個數

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const int N = 2e9+5 , t = 10 , p[t] = { 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 21 , 29 };
int n , sum = 0 , minx;


inline void dfs( int u , int last , int pi , int s )
{
    if( s > sum || s == sum && pi < minx ) sum = s , minx = pi;
    for( register int i = 1 ; i <= last ; i ++ )
    {
        if( (LL)pi * p[u] > n ) break;
        pi *= p[u];
        dfs( u + 1 , i , pi , s * ( i + 1 ) );
    }
    return ;
}


int main()
{
    cin >> n;
    dfs( 0 , 30 , 1 , 1 ); 
    cout << minx << endl;
}

AcWing 199. 餘數之和

首先注意到\(k \mod i = k- \left \lfloor \frac{k}{x} \right \rfloor​\),故能夠轉化爲\(n\times k - \sum_{i=1}^{n}\left \lfloor \frac{k}{i} \right \rfloor\times i​\)

對於任意的\(x\in[1,k]​\),設\(g(x)= \left \lfloor k/ \left \lfloor \frac{k}{x}\right \rfloor \right \rfloor​\)

由於
\[ \left\lfloor\frac{k}{x} \right\rfloor \le \frac{k}{x}\Rightarrow g(x)\ge \left\lfloor \frac{k}{k /x}\right\rfloor = x \]
因此可得
\[ \left\lfloor\frac{k}{g(x)}\right\rfloor\le\left\lfloor\frac{k}{x}\right\rfloor \]

又由於
\[ g(x)\le\frac{k}{\lfloor k/x \rfloor}\Rightarrow\left\lfloor \frac{k}{g(x)}\right\rfloor\ge\left\lfloor \frac{k}{k/\lfloor\frac{k}{x}\rfloor}\right\rfloor = \left\lfloor \frac{k}{x}\right\rfloor \]
因此可得
\[ \left\lfloor \frac{k}{g(x)}\right\rfloor=\left\lfloor \frac{k}{x}\right\rfloor \]
因此可知\(\forall i \in[x,g(x)]​\)\(\lfloor \frac{k}{i}\rfloor​\)恆爲定值

因此在區間[x,g(x)]中\(k\mod i​\)的值是個等差數列,利用高斯公式\(O(1)​\)求和

對於\(\forall x\in[1,n]\)\(\left\lfloor \frac{k}{x}\right\rfloor\)只有\(2\sqrt{n}\)中結果,至關於把\([1,n]\)分紅了\(2\sqrt{n}\)段,每段\(\left\lfloor \frac{k}{x}\right\rfloor\)都相同

每一段的餘數和能夠直接用公式求得,因此最終的複雜度就是\(O(\sqrt{n})​\)

int main()
{
    long long n , k , ans;
    cin >> n >> k;
    ans = n * k;
    for( register int x = 1 , gx ; x <= n ; x = gx + 1 )
    {
        gx = ( k / x ) ? min( k / ( k / x ) , n ) : n;
        ans -= (k/x) * ( x + gx ) * ( gx - x + 1 ) / 2;
    }
    cout << ans << endl;
    return 0;
}

最大公約數

定義

若天然數\(d\)同時是\(a\)\(b\)的約數,則稱\(d\)\(a\)\(b\)的公約數

在全部的公約數中最大的一個就是最大公約數,記做\(gcd(a,b)\)

若天然數\(m\)同時是\(a\)\(b\)的倍數,則成\(m\)\(a\)\(b\)的公約數

在全部的公倍數中最小的一個就是最小公倍數,記住\(lcm(a,b)​\)

同理,咱們也能夠定義三個數以及更多數的最大公約數或最小公倍數

定理

\[ \forall a,b \in N,gcd(a,b)\times lcm(a,b)=a\times b \]

證實:

\(d=gcd(a,b),a_0=\frac{a}{d},b_0=\frac{b}{d}\)

根據定義得\(gcd(a_0,b_0)=1,lcm(a_0,b_0)=a_o\times b_0\)

因此$lcm(a,b)=lcm(a_0\times d,b_0\times d)=lcm(a_0,b_0)\times d=a_0\times b_0 \times d = \frac{a \times b }{d} $

更相減損術

\[ \forall a,b \in N , a > b,有gcd(a,b)=gcd(b,a-b)=gcd(a,a-b) \\ \forall a,b \in N , 有gcd(2a,2b) = 2gcd(a,b) \]

證實

根據最大公約數的定義,後者顯然成立,咱們主要證實前者

對於任意的公約數\(d\),由於\(d|a,d|b\),因此\(d|(a-b)\)所以d也是\(b,a-b\)的公約數

反之亦然成立,故\(a,b\)的公約數集合與\(b,a-b\)的公約數集合相同

歐幾里得算法

\[ \forall a , b \in N , b \ne 0, gcd(a,b) = gcd(b , a \mod b) \]

原理其實和更相減損術是同樣的,作屢次減法直到減不下實際上就等價於作一次取摸

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

歐幾里得算法是經常使用的求最小公倍數的算法,可是若是遇到須要取模和高精度的時候,考慮到高精度實現取模運算比較複雜,能夠考慮用更相減損術來代替歐幾里得,而且時間複雜度是相同的

Luogu P1072 Hankson 的趣味題

爲了方便敘述咱們把\(a_0,a_1,b_0,b_1\)從新定義爲\(a,b,c,d\)

首先能夠最小公倍數的定義可知\(x|d\)

因此咱們能夠直接枚舉\(d\)的因子便可

對於最小公倍數的判斷能夠進行一些變形
\[ \because lcm(a,b)\times gcd(a,b)=a\times b\\ \therefore lcm(x,c)=d\Leftrightarrow d\times gcd(x,c)= x \times c \]

int main()
{
    T = read();
    while( T -- )
    {
        a = read() , b = read() , c = read() , d = read() , ans = 0;
        for( register int i = 1 , j ; i * i <= d ; i ++ )
        {
            if( d % i ) continue;
            if( gcd( i , a ) == b && d * gcd( i , c ) == i * c ) ans ++;
            j = d / i;
            if( gcd( j , a ) == b && d * gcd( j , c ) == j * c ) ans ++;
        }
        printf( "%d\n" , ans );
    }
    
    return 0;
}

互質與歐拉函數

定義
\[ \forall a,b\in N 若gcd(a,b)=1,則稱a,b互質 \]
對於三個數或更多的數,\(gcd(a,b,c)=1 \Leftrightarrow gcd(a,b) = gcd(a,c)=gcd(b,c)=1\)

歐拉函數

\(1\cdots N\)中與N互質的數的個數,被稱爲歐拉函數,記做\(\phi(N)\)

由算數基本定理得\(N= p_{1}^{k_{1}} \times p_{2}^{k_{2}} \times p_{3}^{k_{3}} \times \cdots \ p_{m}^{k_{m}}\)

因此\(\phi(N) = N \times \frac{p_1-1}{p_1}\times \frac{p_2-1}{p_2}\times \cdots \times \frac{p_m-1}{p_m} = N \times \Pi_{p|N}(1-\frac{1}{p})\)

證實

假設\(N​\)有兩個質因子\(p,q​\)

\(1\cdots N\)\(p\)的倍數有\(\frac{N}{p}\)個,同理\(q\)的倍數也有\(\frac{N}{q}\)

咱們把\(\frac{N}{p}\)\(\frac{N}{q}\)排除掉,顯然\(p\times q\) 的倍數被排除了兩次因此要加上\(\frac{N}{p\times q}\)
\[ \phi(N) = N - \frac{N}{p}-\frac{N}{q}+\frac{N}{p\times q} = N\times(1-\frac{1}{p}-\frac{1}{q}+\frac{1}{p\times q}) = N\times(1-\frac{1}{p})\times(1-\frac{1}{q}) \]
根據數學概括法能夠把上訴結論擴展至任意個質因子

因此咱們能夠在作質因數的同時,求出歐拉函數

inline int phi( int x )
{
    register int ans = x;
    for( register int i = 2 ; i * i <= x ; i ++ )
    {
        if( x % i ) continue;
        ans = ans / i *( i - 1 );
        while( x % i == 0 ) n /= i;
    }
    if( n > 1 ) ans = ans  / n *  ( n - 1 );
    return ans ;
}

性質1~2

  1. $\forall n > 1 , 1\cdots n中與互質的數的和爲n\times \phi(n) / 2 $
  2. 若a,b互質,則\(\phi(ab)=\phi(a)\phi(b)\)

積性函數

​ 若是當a,b互質時,知足\(f(ab)=f(a)f(b)\)的函數\(f\)稱爲積性函數

性質3~6

  1. 若f是積性函數,且在算數基本定理中\(n=\Pi_{i=1}^{m} p_i^{c_i}\),則\(f(n) = \Pi_{i=1}^{m} f(p_i^{c_i})\)
  2. \(p\)爲質數,若\(p|n\)\(p^2|n\) , 則\(\phi(n) = \phi( \frac{n}{p})\times p\)
  3. \(p\)爲質數,若\(p|n\)\(p^2 \not|n\),則\(\phi(n) = \phi(\frac{p}{n})\times (p-1)​\)
  4. \(\sum_{d|n}\phi(d)=n​\)

性質4~6,只適用於歐拉函數,並不是全部的積性函數都適用

\(O(n)\)遞推求歐拉函數

const int N = 1e3+5;
int prime[N] , phi[N] , tot ;
bool v[N];

for( register int i = 2 ; i <= n ; i ++ )
{
    if( !v[i] ) prime[ ++ tot ] = i , phi[i] = i - 1;
    for( register int j = 1 ; j <= tot && i * prime[j] <= n ; j ++ ) 
    {
        v[ i * prime[j] ] = 1;
        phi[ i * prime[j] ] = phi[i] * ( i % prime[j] ? prime[j] - 1 : prime[j] );
        if( i % prime[j] == 0 ) break; 
    }   
}

根據性質四、性質5得

補充證實

\(i|p_j\) ,則\(i=k\times p_j\)

因此\(n=i\times p_j = k\times p_j^2\)

\(p_j|n\),且\(p_j^2|n\)

[SDOI2008]儀仗隊

JZYZOJ P1377

AcWing 201. 可見的點 和本題基本相同,不單獨講

首先咱們將原圖從\((1,1)​\)\((n,n)​\)從新標號\((0,0)​\)\((n-1,n-1)​\)

分析題目可知,當\(n=1​\)時,顯然答案是\(0​\),特判斷便可

咱們考慮 \(n \ne1\)的狀況,首先\((0,1),(1,0),(1,1)\)這三個點是必定能看到的

考慮剩餘的點

對於任意的點\((x,y),2\le x,y\le n-1\),若不被其它的點擋住一定知足\(gcd(x,y) = 1\)

證實

若點\((x,y)\) 不知足\(gcd(x,y)=1\),令\(d = gcd(x,y)\)

\((x,y)\)\((0,0)\)連線的斜率\(k=\frac{y}{x} = \frac{y/d}{x/d}\)

因此\((x,y)\)會被\((x/d,y/d)\)擋住

而後咱們把這個圖按照對角線分紅兩個等腰直角三角形

若點\((x,y)​\)在一個三角形中,並知足\(gcd(x,y)=1​\)

則必有一個點\((y,x)​\)在另外一個三角形中,並知足\(gcd(y,x) = 1​\)

因此只統計其中一個三角形便可

如今咱們在\(y<x​\)的三角形中考慮

對於任意一個\(x\),知足條件的\(y\)的個數就是\(\phi(x)\)

因此答案就是
\[ 3+2\times\sum_{i=2}^{n-1}\phi(i) \]
因此們\(O(n)\)的地推歐拉函數的同時求和便可

#include <bits/stdc++.h>
using namespace std;

const int N = 40005;
 
int n , sum , prime[N] , phi[N] , tot ;
bool v[N];

int main()
{
    cin >> n;
    if( n == 1 ) puts("0") , exit(0);
    n --;
    for( register int i = 2 ; i<= n ; i ++ )
    {
        if( !v[i] ) prime[ ++ tot ] = i , phi[i] = i - 1;
        sum += phi[i]; 
        for( register int  j = 1 ; j <= tot && i * prime[j] <= n ; j ++ )
        {
            v[ i * prime[j] ] = 1;
            phi[ i * prime[j] ] = phi[i] * ( i % prime[j] ? prime[j] - 1 : prime[j] );
            if( i % prime[j] == 0 ) break;
        }
    } 
    cout << sum * 2 + 3 << endl;
}

0x33同餘

定義

若正整數\(a\)\(b\)除以\(m\)的餘數相等,則稱\(a\)\(b\)\(m\)同餘,記做\(a \equiv b(mod \ m)\)

性質

  1. 自反性:\(a\equiv a (mod \ m)\)
  2. 對稱性:若\(a\equiv b(mod \ m)\),則\(b \equiv a (mod\ m)\)
  3. 傳遞性:若\(a \equiv b(mod\ m) , b\equiv c(mod\ m)\),則\(a\equiv c(mod\ m)\)
  4. 同加性:若\(a\equiv b(mod\ m)\),則\(a+c\equiv b+c(mod\ m)\)
  5. 同乘性:若\(a\equiv b(mod\ m)\),則\(a\times c \equiv b\times c(mod\ m)\),若\(a\equiv b(mod\ m) , c\equiv d (mod\ m)\),則\(a\times c \equiv b\times d (mod\ m)\)
  6. 同冪性:若\(a\equiv b(mod\ m)\),則\(a^c\equiv b^c(mod\ m)\)
  7. \(a\times b\%\ k=(a\%k)\times(b\%k)\%k\)
  8. \(a\%p=x,a\%q=x,gcd(p,q)=1\),則\(a\%(p\times q) = x\)
  9. 不知足同除,若\(a\equiv b(mod\ m)\)不知足\(a\div c\equiv b\div c(mod\ m)\)

費馬小定理

\(p\)是質數,則對於任意正整數\(a\)都有 \(a^p\equiv a(mod\ p)\)\(a^{p-1}\equiv 1(mod\ p)\)

歐拉定理

若正整數\(a,n\)互質,則\(a^{\phi(n)}\equiv 1(mod\ n)\)其中\(\phi(n)\)是歐拉函數

費馬小定理是歐拉定理的一種特殊狀況

歐拉定理推論

若正整數\(a,n\)互質,則對於任意的正整數\(b\),有\(a^b\equiv a^{b\ mod \ \phi(n)}(mod \ n)​\)

在一些計數的問題中,經常要求對結果取模,但面對乘方時,沒法直接取模,能夠先把底數對\(p\)取模,指數對\(\phi(p)\)取模,再計算乘方

特別的,當不保證\(a,n\)互質時,若\(b>\phi(n)\),有\(a^b\equiv a^{b\ mod \ \phi(n)+\phi(n)}(mod \ n)\)

這也就意味着即便不保證互質,咱們也可把乘方運算縮小到容易計算的規模

AcWing 202. 最幸運的數字

\(x\)個8連在一塊兒組成的整數可寫做\(8(10^x-1)/9\),題目就是要求咱們求最小的\(x\)知足\(L|8(10^x-1)/9\)

\(d = gcd(L,8)\)
\[ L|\frac{8(10^x-1)}{9}\Leftrightarrow 9L|8(10^x-1)\Leftrightarrow \frac{9L}{d}|10^x-1\Leftrightarrow 10^x\equiv1(mod\ \frac{9L}{d}) \]
引理:

若正整數\(a,n​\)互質,則知足\(a^x\equiv 1(mod n)​\)的最小正整數\(x_0​\)\(\phi(n)​\)的約數

證實

反證法:假設知足\(a^x\equiv 1(mod n)\)的最小正整數\(x_0\)不是\(\phi(n)\)的約數

\(\phi(n)=qx_0+r,(0<r<x_0)\),由於\(a^{x_0}\equiv1(mod\ n)\),因此\(a^{qx_0}\equiv 1(mod\ n)\)

又因歐拉定理得\(a^{\phi(n)} \equiv 1(mod\ n)\),因此可得\(a^{r}\equiv 1(mod \ n)\)

假設不成立,因此假設錯誤

證畢

根據以上定理,咱們只需求出\(\phi(\frac{9L}{d})\),枚舉他全部的約數,用快速冪逐一檢查條件便可

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const LL INF = 1e18;
LL l, mod , ret , p ;

inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' ) x = ( x << 3 ) + ( x << 1 ) + ch - '0' , ch = getchar();
    return x;
}

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

inline LL power(LL x , LL  p )
{
    register LL ans = 1;
    while( p )
    {
        if( p & 1 ) ans = ( ans * x ) % mod;
        x = ( x * x ) % mod;
        p >>= 1; 
    }
    return ans;
}

inline int phi( int x )
{
    register int ans = x;
    for( register int i = 2 ; i * i <= x ; i ++ )
    {
        if( x % i ) continue;
        ans = ans / i * ( i - 1 );
        while( x % i == 0 ) x /= i;
    }
    if( x > 1 ) ans = ans / x * ( x - 1 );
    return ans;
}

inline void work()
{
    mod = l / gcd( 8 , l ) * 9 , p = phi( mod ) ,ret = INF;
    for( register LL i = 1 ; i * i <= p ; i ++)
    {
        if( p % i ) continue;
        if( power( 10 , i  ) ==  1 ) ret = min( ret , i );
        if( power( 10 , p / i ) == 1 )  ret = min( ret , p / i ); 
    }
    return ;
}

int main()
{
    for( register int i = 1 ; i ; i ++  )  
    {
        l = read();
        if( l == 0 ) break;
        work() , printf( "Case %d: %d\n" , i , (ret == INF ? 0 : ret ) );
    } 
    return 0;
}

裴蜀定理

對於任意的\(a\),\(b\)存在無窮多組整數對\((x,y)\)知足不定方程\(ax+by=d\),其中\(d=gcd(a,b)\)

\(ax+by=d \ (d=gcd(a,b))\)這個方程叫丟番圖方程

擴展歐幾里得算法

在求\(gcd(a,b)\)的同時,能夠求出關於\(x,y\)的不定方程\(ax+by=d\)的一組解

考慮遞歸計算:假設已經算出了\((b\%a,a)\)的一組解\((x_{0} , y_{0})\)知足\((b\%a)x_0+ay_0=d\)

能夠獲得\((b-a\lfloor \frac{b}{a} \rfloor)x_0+ay_0 = d\)

整理獲得\(a(y_0 - \lfloor \frac{b}{a} \rfloor x_0) + bx_0 = d\)

爲了方便表示\(swap(x_0,y_0)\)\(a(x_0 - \lfloor \frac{b}{a} \rfloor y_0) + by_0 = d\)

就能夠獲得一組解\(((x_0 - \lfloor \frac{b}{a} \rfloor y_0),y_0)\)

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

擴展歐幾里得一元線性同餘方程

解關於\(x\)的一元線性同餘方程\(ax\equiv b(mod\ m)\)

已知擴展歐幾里得能夠解\(ax_0+by_0=d,(d = gcd(a,m))\)

將原方程變形得\(ax + my = b\)

經過擴展歐幾里得可解出\(ax^\prime + my^\prime = d\)

\(b\%d==0\)\(ax + my = b\)有解,反之無解

方程\(ax^\prime + my^\prime = d\)兩邊同乘\(\frac{b}{d}\)\(a\frac{b}{d}x^\prime+m\frac{b}{d}y^\prime = b\)

咱們就把原方程解出來\(x = \frac{b}{d}x^\prime\)

乘法逆元

在以前咱們提到過,除法不能直接取模,可是能夠用逆元

\(x\)知足\(ax\equiv 1(mod \ n )\),則稱\(x\)\(a\)在模\(n\)意義下的逆元記做\(a^{-1}\)

固然知足條件的逆元不止一個,一般狀況下咱們只用最小正整數的逆元,而且逆元也不在任何條件下都有逆元

求逆元

下面介紹三種最經常使用的求逆元的方法

費馬小定理

費馬小定理 \(a^{p-1} \equiv 1(mod\ p)\),其中\(p\)爲素數

根據費馬小定理變形得\(aa^{p-2}\equiv 1(mod\ p)\)

\(a^{p-2}\% p\)就是逆元,直接快速冪求得

inline int invers(int x,int p)
{
    return ksm(x,mod-2,p) % p;
}

擴展歐幾里得

根據逆元的定義\(ax \equiv 1(mod\ m)\)\(ax+my=1\)

解這個丟番圖方程可得\(x\%m\)就是逆元,同時可得逆元存在的條件\(gcd(a,m) = 1\)

inline int invers(int a,int mod)
{
    register int x,y;
    exgcd(a,mod,x,y);
    return (x % mod + mod) % mod;
}

線性遞推

\(a^{-1} =\displaystyle -\left \lfloor \frac{m}{a}\right\rfloor\times(m\%a)^{-1}\)

const int N = 1005;
int inv[N] = {};

inline void invers(int n,int mod)
{
    inv[1] = 1;
    for(register int i = 2;i <= n;i ++)
            inv = (long long)(mod - mod / i) * inv[mod % i] % mod;
    return ;
}

線性同餘方程

給定整數\(a,b,m\),求一個整數\(x\)知足\(a\times x\equiv b(mod \ m)\),或者無解。由於未知數的指數爲一,因此成爲一次同餘方程或線性同餘方程

解關於\(x\)的一元線性同餘方程\(ax\equiv b(mod\ m)\)

已知擴展歐幾里得能夠解\(ax_0+by_0=d,(d = gcd(a,m))\)

將原方程變形得\(ax + my = b\)

經過擴展歐幾里得可解出\(ax^\prime + my^\prime = d\)

\(b\%d==0\)\(ax + my = b\)有解,反之無解

方程\(ax^\prime + my^\prime = d\)兩邊同乘\(\frac{b}{d}\)\(a\frac{b}{d}x^\prime+m\frac{b}{d}y^\prime = b\)

咱們就把原方程解出來\(x = \frac{b}{d}x^\prime\)

中國剩餘定理CRT

又稱中國餘數定理孫子定理,最先可見於中國南北朝時期(公元5世紀)的數學著做《孫子算經》卷下第二十六題,叫作「物不知數」問題,原文以下:

有物不知其數,三三數之剩二,五五數之剩三,七七數之剩二。問物幾何?

簡單來講就求一元線性同餘方程組
\(\left\{\begin{matrix} x \equiv a_1(mod\ m_1)\\x \equiv a_2(mod\ m_2)\\\vdots\\x \equiv a_n(mod\ m_n) \end{matrix}\right.\)

對於這樣的一個方程組若是任意的兩個\(m\)都互素,則必定有解
因此中國剩餘定理就是解但\(m\)互素的狀況

咱們嘗試來構造這樣一組解

\(M = m_1 \times m_2 \times \cdots \times m_n,M_i = \frac{M}{m_i}\)

\(M_i\)只有在模\(m_i\)時不爲零,模其餘\(m\)都是零

因爲咱們但願在模\(m_i\)時獲得\(a_i\)因此乘上\(a_it_i\)

其中\(t_i\)\(M_i\)在模\(m_i\)時的逆元

因此最小正整數解就是\(x = \sum_{i=1}^{n}a_it_iM_i\),通解就是\(x+kM\)

若是題目說明了的\(m\)都是素數則能夠用費馬小定理

inline int crt()
{
    register int ans = 0,M = 1;
    for(register int i = 1;i <= n;i ++) M *= m[i];
    
    for(register int i = 1;i <= n;i ++)
    {
        register int Mi = M/m[i],invers = ksm(Mi,m[i]-2,m[i]);
        ans = (ans + a[i]*invers*Mi) % M;
    }
    return ans;
}

若是題目沒有說明咱們仍是用擴展歐幾里得來求逆元

inline int crt()
{
    register int M = 1, ans = 0;
    for(register int i = 1;i <= n; i ++) M *= m[i];
    for(register int i = 1;i <= n; i ++)
    {
        register LL Mi = M/m[i],x,y;
        exgcd(Mi,m[i],x,y);
        ans = ((ans + a[i] * Mi * x) % M + M) % M;
    }
    return ans;
}

證實中國剩餘定理必定有解

咱們舉這個同餘方程爲例

\(\left\{\begin{matrix} x \equiv a_1(mod\ m_1)\\x \equiv a_2(mod\ m_2)\\ \end{matrix}\right.\)

假設咱們分別解出

\(\left\{ \begin{matrix} x_1 = a_1+k_1 \times m_1 \\ x_2 = a_2 + k_2 \times m_2 \end{matrix}\right.\)

由於 \(x_1 = x_2\)

因此\(a_1+k_1\times m_1 = a_2 + k_2 \times m_2\)

整理得\(k_2\times m_2 - k_1\times m_1 = a_1-a_2\)

這就是\(ax+by=d\)的形式

因此當\(gcd(m_1,m_2)|a_1-a_2\) 時必定有解

又由於\(m_1\)\(m_2\)互質

因此\(gcd(m_1,m_2) = 1\)

顯然中國剩餘定理必定有解

擴展中國剩餘定理EXCRT

擴展中國剩餘定理就是解一元線性同餘方程組

\(\left\{\begin{matrix} x \equiv a_1(mod\ m_1)\\x \equiv a_2(mod\ m_2)\\\vdots\\x \equiv a_n(mod\ m_n) \end{matrix}\right.\)

可是這裏的\(m\)不要求互素,這是與中國剩餘定理不一樣的地方

假設們我已經求出前\(k-1\)個等式的最小整數解\(x\)

\(M = LCM(m_1,m_2,\cdots,m_{k-1})\)

則前\(k-1\)個等式的通解爲\(x+tM\ (t\in Z)\)

咱們要找\(x+t^\prime M \equiv a_k (mod\ m_k)\)

新的解\(x^\prime = x + t^\prime M\)

因此解\(n\)個方程就能夠求出最後的解

怎麼解\(x+t^\prime M \equiv a_k (mod\ m_k)\)呢?

咱們能夠化成\(t^\prime M \equiv a_k - x(mod\ m_k)\)

咱們能夠用擴展歐幾里得解出\(t^\prime\),這樣咱們就構造出呢前\(k\)個方程的一組特解

inline int excrt()
{
    register int x = a[1],M = m[1],t,y,c,d,mod; 
    for(register int i = 2;i <= n;i ++)
    {
        c = ((a[i] - x) % m[i] + m[i]) % m[i]; d = exgcd(M,m[i],t,y);
        if(c%d) return -1;
        mod = m[i] / d;
        t = ((t * (c/d)) % mod + mod) % mod;
        x += t * M;
        M *= mod;
        x = (x + M) % M;
    }
    return x;
}

PS

  • 對於第一個方程\(x=a_1 , M= m_1\)
  • \(c = a_i - x\)
  • \(x \% \frac{M\times m_i}{d} = (x\%\frac{m_i}{d})\%M\) 因此\(mod = \frac{m_i}{d}\)

Luogu P1082 同餘方程

很是很是落的模板題,記得用long long

#define LL long long

int main()
{
    LL a , b ;
    cin >> a >> b;
    LL x , y;
    exgcd( a , b , x , y );
    cout << ( x % b + b ) % b << endl;
    return 0;
}

Luogu P4777 擴展中國剩餘定理

本題有弱化版AcWing 204. 表達整數的奇怪方式

很是裸的\(excrt\),記住用快速乘

0x34 矩陣乘法

矩陣

數學上,一個\(n\times m\)的矩陣是一個有\(m\)\(n\)列的元素排列成的矩形陣列。矩陣裏的元素能夠是數字、符號或數學式。一下是一個由\(6\)個元素構成的\(2\)\(3\)列的矩陣
\[ \begin{bmatrix} 1 & 2 & 3\\ 4 & 5 & 6 \end{bmatrix} \]
大小相同(行列數都相同)的矩陣能夠相加減,具體作法是每一個對應位置上的元素相加減

特殊的矩陣

方陣

若是一個行數和列數都相同的矩陣,咱們稱做方陣,下面是一個\(3\times3\)的方陣
\[ \begin{bmatrix}1 & 2 & 3\\4&5&6\\7&8&9\end{bmatrix} \]

單位矩陣

對於\(n\times n\)的方陣,若是主對角線上的元素爲\(1\),其他元素全爲\(0\)咱們稱爲\(n\)階單位矩陣,通常用\(In\)\(En\)表示,一般狀況下簡稱\(I\)\(N\),下面是一個\(4\)階單位矩陣
\[ \begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{bmatrix} \]
主對角線:從左上到右下的對角線

反對角線:從右上到左下的對角線

矩陣乘法

詳細的矩陣乘法的運算過程能夠參考這個視頻
\[ \begin{bmatrix} 3 & 1 & -1\\ 2 & 0 & 3 \end{bmatrix} \times \begin{bmatrix} 1&6\\ 3&-5\\ -2&4\\ \end{bmatrix} = \begin{bmatrix} 3\times 1+1\times3+(-1)\times(-2)&2\times1+0\times3+3\times(-2)\\ 3\times6+1\times(-5)+(-1)\times4&2\times6+0\times(-5)+3\times4 \end{bmatrix} =\begin{bmatrix} 8&9\\-4&24\end{bmatrix} \]
定義

\(A\)\(n\times m\)的矩陣\(B\)\(m\times p\) 的矩陣,則\(C=A\times B\)\(n\times p\)的矩,而且
\[ \forall i\in [1,n], \forall j\in[1,p]:c_{i,j}=\sum_{k=1}^{m}A_{i,k}\times B_{k,j} \]
也就是說,參與矩陣乘法的矩陣第一個矩陣的列數必須等於第二個矩陣的行數,不然不能運算

矩陣乘法知足結合律\((A\times b)\times C = A \times (B \times C)\),知足分配律$(A+B)\times C = A\times C + B \times C \(,但不知足交換律\)A\times B \ne B \times A$

方陣乘冪

方陣乘冪是指,\(A\)是一個方陣,將\(A\)連乘\(n\)次,即\(C = A^n\)

若不是方陣則不能進行方陣乘冪

因爲矩陣乘法知足結合律,因此能夠用快速冪的實現來實現矩陣快速冪

矩陣乘法的應用

矩陣乘法奧妙在於:

  1. 很容易將有用的狀態儲存在一個矩陣中
  2. 經過狀態矩陣和狀態轉移矩陣相乘快速的獲得一次\(DP\)的值(這個\(DP\)的狀態轉移方程必須是一次的)
  3. 求矩陣乘法的結果是要作不少次乘法,這樣的效率很是慢甚至比不上直接遞推動行\(DP\)的狀態轉移,但因爲矩陣乘法知足結合律,能夠先算後面的轉移矩陣,即用快速冪,迅速的處理好後面的轉移矩陣,再用初始矩陣乘上後面的轉移矩陣獲得結果,複雜度是$O( log(n) ) $的

Loj 10219.矩陣 A×B

矩陣乘法的模板題

const int N = 105;
int n , m , q , a[N][N] , b[N][N] , c[N][N];

int main()
{
    n = read() , m = read();
    for( register int i = 1 ; i <= n ; i ++ )
    {
        for( register int j = 1 ; j <= m ; j ++ ) a[i][j] = read();
    }
    q = read();
    for( register int i = 1 ; i <= m ; i ++ )
    {
        for( register int j = 1 ; j <= q ; j ++ ) b[i][j] = read();
    }
    //代碼的核心矩陣乘法
    for( register int i = 1 ; i <= n ; i ++ )
    {
        for( register int j = 1 ; j <= q ; j ++ )
        {
            for( register int k = 1 ; k <= m ; k ++ ) c[i][j] += a[i][k] * b[k][j];
        }
    }
    for( register int i = 1 ; i <= n ; i ++ )
    {
        for( register int j = 1 ; j <= q ; j ++ ) printf( "%d " , c[i][j] );
        puts("");
    }
    return 0;
}

Loj 10220.Fibonacci 第 n 項

本題看似簡單,但巨大的數據範圍開數組確定開不下,而且線性求解效率也很低

咱們知道
\[ f[i]=1\times f[i-1] + 1\times f[i-2]\ (1)\\ f[i-1] = 1\times f[i - 1]+0\times f[i-2]\ (2) \]
觀察上面兩個式子,咱們能夠把他改寫成矩陣乘法的形式
\[ \begin{bmatrix}f[i]\\f[i-1]\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\times\begin{bmatrix}f[i-1]\\f[i-2]\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\times \begin{bmatrix}1&1\\1&0\end{bmatrix}\times\begin{bmatrix}f[i-2]\\f[i-3]\end{bmatrix} \]
根據數學概括法們就能獲得
\[ \begin{bmatrix}f[n]\\f[n-1]\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-2}\times\begin{bmatrix}f[2]\\f[1]\end{bmatrix} \]
以前咱們提到過矩陣能夠快速冪,因此咱們就能夠在\(O(log(n))\)的複雜度內求出\(f[n]\)

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const int N = 5;
LL n , m ;

struct matrix
{
    int x , y ;
    LL v[N][N];
    inline friend matrix operator * ( matrix a , matrix b )
    {
        register matrix cur;
        cur.x = a.x , cur.y = b.y;
        for( register int i = 1 ; i <= a.x ; i ++ )
        {
            for( register int j = 1 ; j <= b.y ; j ++ )
            {
                cur.v[i][j] = 0;
                for( register int k = 1 ; k <= a.y ; k ++ )
                {
                    cur.v[i][j] += a.v[i][k] * b.v[k][j];
                    cur.v[i][j] %= m; 
                }
            }
        }
        return cur;
    }
}f , res , cus ;


inline void matrix_I( matrix & k , int t )
{
    k.x = k.y = t;
    for( register int i = 1 ; i <= t ; i ++ )
    {
        for( register int j = 1 ; j <= t ; j ++ )
        {
            if( i == j ) k.v[i][j] = 1;
            else k.v[i][j] = 0;
        }
    }
    return ;
}

inline matrix matrix_power( matrix x , int k )
{
    register matrix t; matrix_I( t , 2 );
    while( k )
    {
        if( k & 1 ) t = t * x;
        x = x * x;
        k >>= 1;
    }
    return t ;
}

inline LL fib()
{
    if( n <= 2 ) return 1;
    f.x = f.y = 2;
    f.v[1][1] = f.v[1][2] = f.v[2][1] = 1 , f.v[2][2] = 0;
    res.x = 2 , res.y = 1;
    res.v[1][1] = res.v[2][1] = 1;
    f = matrix_power( f , n - 2 );
    res = f * res;
    return res.v[1][1] ;
}


int main()
{
    cin >> n >> m;
    cout << fib() << endl;
    return 0;
}

Loj 10221.Fibonacci 前 n 項和

本題在上一題的基礎上增長了求和,咱們依舊要構造狀態轉移矩陣
\[ s[n] = 1 \times s[n-1] + 1 \times f[n] + 0 \times f[n-1]\\ f[n+1] = 0 \times s[n-1] + 1 \times f[n] + 1 \times f[n-1]\\ f[n] = 0 \times s[n-1] + 1 \times f[n] + 0 \times f[n-1] \]
因此我麼能夠根據上面的三個式子推出狀態轉移矩陣
\[ \begin{bmatrix} s[n] \\ f[n+1] \\ f[n] \end{bmatrix} = \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 1 \\ 0 & 1 & 0 \end{bmatrix} \times \begin{bmatrix} s[n-1] \\ f[n] \\ f[n-1] \end{bmatrix} \]
而後就依舊是矩陣快速冪

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const int N  = 5;
int n , m ;

struct matrix
{
    LL v[N][N];
    int x , y;
    inline friend matrix operator * ( matrix a , matrix b )
    {
        register matrix cur;
        cur.x = a.x , cur.y = b.y;
        for( register int i = 1 ; i <= cur.x ; i ++ )
        {
            for( register int j = 1 ; j <= cur.y ; j ++ )
            {
                cur.v[i][j] = 0;
                for( register int k = 1 ; k <= a.y ; k ++ ) cur.v[i][j] = ( cur.v[i][j] + a.v[i][k] * b.v[k][j] ) % m;
            }
        }
        return cur;
    }
} res , f;


inline void matrix_I( matrix & k , int t )
{
    k.x = k.y = t;
    for( register int i = 1 ; i <= t ; i ++ )
    {
        for( register int j = 1 ; j <= t ; j ++ ) k.v[i][j] = ( i == j ) ? 1 : 0;
    }
    return ;
}

inline matrix matrix_power( matrix x , int k )
{
    register matrix t ; matrix_I( t , 3 );
    while( k )
    {
        if( k & 1 ) t = t * x;
        x = x * x;
        k >>= 1;
    }
    return t;
}

inline LL fib()
{
    if( n == 1 ) return 1;
    if( n == 2 ) return 2;
    f.x = f.y = res.x = 3 , res.y = 1; 
    f.v[1][1] = f.v[1][2] = f.v[2][2] = f.v[2][3] = 1 , f.v[3][2] = res.v[1][1] = res.v[2][1] = res.v[3][1] = 1;
    f.v[1][3] = f.v[2][1] = f.v[3][1] = f.v[3][3] = 0 ;
    f = matrix_power( f , n - 1 );
    res = f * res;
    return res.v[1][1];
}


int main()
{
    cin >> n >> m;
    cout << fib() << endl;
}

Lougu P1939 矩陣加速

矩陣快速冪的模板題,推到過程很簡單
\[ a[i] = 1\times a[i-1] + 0\times a[i-2]+1\times a[i-3]\\ a[i-1] = 1\times a[i-1] + 0\times a[i-2]+0\times a[i-3]\\ a[i-2] = 0\times a[i-1] + 1\times a[i-2]+0\times a[i-3]\\ \Downarrow \\ \begin{bmatrix}f[i]\\f[i-1]\\f[i-2]\end{bmatrix}=\begin{bmatrix}1&0&1\\1&0&0\\0&1&0\end{bmatrix}\times \begin{bmatrix}f[i-1]\\f[i-2]\\f[i-3]\end{bmatrix} \]
直接暴力矩陣快速冪是能夠過的,但有沒有優化呢?

咱們考慮離線來作這道題

首先咱們把全部的數據讀入,而後離散化,並排序

對與\(ans_i\)咱們能夠計算\(d=n_i-n_i\)獲得一個差值

而後只需將\(ans_{i-1}\)乘上狀態轉移矩陣\(f\)\(d\)次方,就是\(ans_i\)

能夠經過這種操做來減小屢次重複的運算

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const int N = 105 , M = 5 , mod = 1e9 + 7;
int n , key[N] , val[N] , ans[N];

struct matrix
{
    int x , y;
    LL v[M][M];
    
    matrix() { memset( v , 0 , sizeof(v) ); }
    
    inline friend matrix operator *  (matrix a , matrix b )
    {
        register matrix cur;
        cur.x = a.x , cur.y = b.y;
        for( register int i = 1 ; i <= cur.x ; i ++ )
        {
            for( register int j = 1 ; j <= cur.y ; j ++ )
            {
                cur.v[i][j] = 0;
                for( register int k = 1 ; k <= a.y ; k ++ ) cur.v[i][j] = ( cur.v[i][j] + a.v[i][k] % mod * b.v[k][j] % mod ) % mod;
            }
        }
        return cur;
    }
} f , res;


inline LL read()
{
    register LL x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
}

inline void matrix_I( matrix &t , int k)//構造單位矩陣
{
    t.x = t.y = k;
    for( register int i = 1 ; i <= k ; i ++ ) t.v[i][i] = 1;
}

inline matrix matrix_power( matrix x , int k )//矩陣快速冪
{
    if( k == 1 ) return x;
    register matrix t; matrix_I( t , 3 );
    while( k )
    {
        if( k & 1 ) t = t * x ;
        x = x * x;
        k >>= 1;
    }
    return t;
}

inline void init()
{
    f.x = f.y = 3;
    memset( f.v , 0 , sizeof( f.v ) );
    f.v[1][1] = f.v[1][3] = f.v[2][1] = f.v[3][2] = 1 ;
}


int main()
{
    n = read();
    for( register int i = 1 ; i <= n ; i ++ ) key[i] = val[i] = read();
    sort( val + 1 , val + 1 + n ) ;
    for( register int i = 1 ; i <= n ; i ++ ) key[i] = lower_bound( val + 1 , val + 1 + n , key[i] ) - val;
    res.x = 3 , res.y = res.v[1][1] = res.v[2][1] = res.v[3][1] = 1;
    val[0] = 3;
    for( register int i = 1 ; i <= n ; i ++ )
    {
        if( val[i] <= 3 ) { ans[i] = 1 , val[i] = 3 ; continue; }
        register int d = val[i] - val[ i - 1 ];//計算差值
        if( d == 0 ) //特殊狀況
        {
            ans[i] = ans[ i - 1 ] ;
            continue;
        }
        init();//初始化 矩陣f 
        f = matrix_power( f , d );
        res = f * res;
        ans[i] = res.v[1][1] % mod;
    }
    for( register int i = 1 ; i <= n ; i ++ ) printf( "%d\n" , ans[ key[i] ] );
    return 0;
}

0x35組合數學

加法原理

若完成一件事有\(n\)類,第\(i\)類有\(a_i\)中方法,那麼完成這件事共有\(a_1+a_2+\cdots+a_n\)種方法

乘法原理

若完成一件事\(n\)個步驟,第\(i\)個步驟有\(a_i\)種方法,那麼完成這件事共有\(a_1\times a_2\times \cdots\times a_n\)種方法

排列數

假設有這樣一個問題:如今有甲乙丙丁四個小朋友,老師想從中挑出兩個小朋友排成一列,有幾種排法。
很容易枚舉出來,有12種排法:

甲乙,甲丙,甲丁,乙甲,乙丙,乙丁,丙甲,丙乙,丙丁,丁甲,丁乙,丁丙

但問題假如變成從\(66666\)個小朋友中挑出\(2333\)個小朋友有幾種排法,顯然枚舉是不現實的。咱們須要一個通用的計算方法。

排列就是指這類問題。它的定義是:從\(m\)個不一樣的元素中取出\(n\) (\(0\leq n\leq m\))個元素排成一列的方法數。記做\(A_{m}^{m}\)
咱們能夠一步一步的考慮這個問題,首先從\(m\)個小朋友中抽取一個放在第一個有\(m\)種方法。以後從剩下的\(m-1\)箇中挑一個放在第二個即有\(m-1\)中方法。以此類推,根據乘法原理咱們能夠獲得
\[ A_{m}^{n} = m \times (m-1) \times (m-2) \times \cdots \times (m-n+1) \]
經過化簡咱們能夠獲得
\[ A_{m}^{n} = \frac{m!}{(m-n)!} \]
PS:階乘\(n! = 1 \times 2 \times 3 \times \cdots \times n\)。特別的咱們規定\(0! = 1\)

組合數

若是老師只是先從4個小朋友中挑出2人組成一組,不用考慮排列順序呢麼有幾種呢?很顯然經過枚舉得知有6種

甲乙,甲丙,甲丁,乙丙,乙丁,丙丁

組合數就是指這類問題。它的定義是:從\(m\)個不一樣的元素中取出\(n\) (\(0\leq n\leq m\))個元素組成一組的方法數。記做\(C_{m}^{m}\)。注意與排列不一樣的是不考慮順序。
呢麼咱們就要從新推到,類比剛纔的結論咱們得知排列只是將組合的每種結果進行了不一樣的排序。反而言之對於組合來講,排序是重複的。重複的狀況是每一個組合的排序。呢麼對於每一個組合重複了多少種狀況呢。至關於從\(n\)個小朋友種抽出\(n\)個小朋友排一對,也就是
\[ A_{n}^{n} = \frac{n!}{(n-n)!} = \frac{n!}{0!} = n!\]
\[ 排序是對組合的每種狀況進行排序,根據乘法原理 \]
A_{m}^{n} = C_{m}^{n} \times A_{n}^{n}
\[ 因此咱們獲得 \]
C_{m}^{n} = \frac{m!}{(m-n)! \times n!}
$$

性質:

  1. \(C_n^m=C_n^{n-m}\)
  2. \(C_n^m=C_{n-1}^{m}+C_{n-1}^{m-1}\)
  3. \(C_n^0+C_n^1+\cdots +C_n^n=2^N\)
  4. \(C_n^0+C_n^2+C_n^4+\cdots = C_n^1+C_n^3+C_n^5\cdots=2^{n-1}\)

組合數的計算

加法遞推\(O(n^2)\)

利用性質2,邊界條件\(C_n^0=C_n^n=1\),其實就是楊輝三角

for( register int i = 0 ; i <= n ; i ++ )
{
    c[i][0] = 1;
    for( register int j = 1 ; j <= i ; j ++) c[i][j] = c[i-1][j] + c[i-1][j-1];
}

乘法地推\(O(n)\)

\(C_n^m=\frac{n-m+1}{n}\times C_n^{m-1}\),邊界條件\(C_n^0=1\)。必須先乘後除,否則可能除不開

能夠利用性質\(1​\),減小部分運算

c[0] = 1;
for( register int i = 1 ; i * 2 <= n ; i ++ ) c[i] = c[n-i] = ( n - i + 1 ) * c[ i - 1 ] / i;

生成全排列

一些題目可能會給定一串數字,要求找到其中的某一個順序,暴力的作法就是生存這個序列的全排列,而後暴力判斷

這裏提供一個\(STL​\)來生成全排列next_permutation

next_permutation是生成當前序列的下一個序列,直到整個序列單調遞減

int a[3];

do
{
    for( register int i = 0 ; i < 3 ; i ++ ) cout << a[i] << ' ';
    cout << endl;
}while( next_permutation( a , a + 3 ) );

固然next_permutation也能夠只針對某一段生成排列,修改參數便可,同時也支持自定義排序方式,相似sort

注意next_permutation操做的序列必須是單調遞增,否則只能生成當前序列的下一個序列,沒法生成全部的序列

枚舉子集

若是用一個數的二進制來表示一個集合,那麼就能夠用下面的方法生成當前集合的全部子集,並能作到不重不漏

for( register int i = s ; i ; i = ( i - 1 ) & s );

二項式定理

\[ (a+b)^n=\sum_{k=0}^{n}C_n^ka^{n-k}b^{k} \]

NOIP2011計算係數

二項式定理的模板題直接計算便可

#include <bits/stdc++.h>
#define LL long long
using namespace std;


const int N = 1e6+5;
const LL mod = 10007;
LL a,b,k,n,m,result = 1,t[N];


inline LL gcd(LL a,LL b) {return !b?a:gcd(b,a%b);}

inline LL ksm(LL a,LL b)
{
    register LL ans = 1;
    while(b)
    {
        if(b & 1) ans = (ans * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ans % mod;
}


int main()
{
    cin >> b >> a >> k >> n >> m;
        
    result = (result * ksm(b,n)) % mod;
    result = (result * ksm(a,m)) % mod;
    
    n = min(n,m);
    
    for(register int i = 1,j = k - n + 1;i <= n;i ++,j ++) t[i] = j;
    
    for(register int i = 2;i <= n;i ++)
    {
        register LL x = i,y;
        for(register int j = 1;j <= n;j ++)
        {
            if(x > t[j]) y = gcd(x,t[j]);
            else y = gcd(t[j],x);
            if(y == 1) continue;
            x /= y; t[j] /= y;
            if(x == 1) break;
        }
    }
    
    for(register int i = 1;i <= n;i ++) result = (result * t[i]) % mod;
    
    cout << result << endl;
}

Catalan數列

給定\(n\)\(0\)\(n\)\(1\),他們按照某種順序排列成長度爲\(2n\)的序列,知足任意前綴中\(0\)的個數都很多於\(1\)的個數的序列的數量爲
\[ Cat_n=\frac{1}{n+1}\times C_{2n}^{n} \]
推論:

  1. \(n\)個左括號和\(n\)個右括號組成的合法序列爲\(Cat_n\)
  2. \(1,2,3,\cdots n\)通過一個棧造成的合法出棧順序爲\(Cat_n\)
  3. \(n\)個節點構成不一樣的二叉樹的數量爲\(Cat_n\)
  4. 在平面直角

Lucas定理

\(p\)是質數,則對於任意的整數\(1\le m \le n\),有:
\[ C_n^m=C_{n\mod p}^{m\mod p}\times C_{n/p}^{m/p}(\mod P) \]

Loj 10228. 組合

盧卡斯定理的模板題,組合數根據定義直接求

Luogu P3807盧卡斯定理小細節不一樣

#include <bits/stdc++.h>
#define LL long long 
using namespace std;

LL p;

inline int read()
{
    register int x = 0;
    register char ch = getchar();
    while( ch < '0' || ch > '9' ) ch = getchar();
    while( ch >= '0' && ch <= '9' )
    {
        x = ( x << 3 ) + ( x << 1 ) + ch - '0';
        ch = getchar();
    }
    return x;
}

inline LL power( LL x , LL y )
{
    register LL res = 1;
    while( y )
    {
        if( y & 1 ) res = ( res * x ) % p;
        x = ( x * x ) % p;
        y >>= 1;
    } 
    return res;
} 

inline LL inv ( LL x ) { return power( x , p - 2 ) % p ; }

inline LL C ( LL n , LL m )
{
    if( m > n )  return 0;
    LL a = 1 , b = 1;
    for( register LL i = n - m + 1 ; i <= n ; i ++ ) a = a * i % p;
    for( register LL i = 2 ;i <= m ; i ++ ) b = b * i % p;
    return a * inv( b );
}

inline LL lucas( LL n , LL m )
{
    if( !m ) return 1;
    return C( n % p , m % p ) * lucas( n / p , m / p ) % p;
}

int main()
{
    for( register int T = read() , n , m ; T >= 1 ; T -- ) n = read() , m = read() , p = read() , printf( "%lld\n" , lucas( n , m ) ); 
    return 0;
}

0x3e補充證實

part 1.

已知\(t = x+y,a^x\equiv1(mod\ n),a^t \equiv 1(mod \ n)\)

求證\(a^y\equiv 1(mod\ n)\)

證實
\[ 由已知式子得a^x=k_1 \times n+1\\ 設a^y=k_2\times n + p\\ 則a^t=a^x\times a^y=k_1k_2n^2+(tk_1+k_2)n+t\\ 又由於a^t\% n=1,因此t=1\\ 因此a^y\equiv1(mod\ n) \]

證實素數是無數個

假設數是\(n\)個,每一個素數是\(p_i\)

\(P = \Pi_{i=1}^{n} pi + 1\)

由於任何一個數均可以分解成多個質數相乘

因此\(P\)除以任何一個質數都餘\(1\)

顯然\(P\)就也是一個質數

與假設矛盾,因此假設錯誤

因此質數是無限個

PS

\(P = \Pi_{i=1}^{n} pi + 1\)不是質數的通項公式

舉例$ n = 2 $

\(P = 2 \times 3 + 1 = 7\)

顯然第三個質數是\(5\)

因此這個式子只能用於證實素數是無限個,並非質數的通項公式

目前人類尚未發現質數的通項公式,只有近似公式

求斐波那契數列通項公式

已知\(F_{n+2} = F_{n+1} + F_n\) 其中\(F_1 = F_2 = 1\) ,求\(F_n\)通項公式

首先令首項爲\(1\)公比爲\(x\)的等差數列\(\{ x^n \}\) 知足\(F_n\)的遞推式

代入遞推式得\(x^{n+2}=x^{n+1} + x^{n}\)

整理得\((x^2-x-1)x^n = 0\)

由於$x^n\neq 0 $

因此\(x^2-x-1=0\)

解得\(x_1=\frac{1+\sqrt{5}}{2}\)\(x_2=\frac{1-\sqrt{5}}{2}\)

因此數列\({x_1^n}\)\({x_2^n}\)都知足\(F_n\)的遞推公式,但\({x_1^n}\)\({x_2^n}\)都不是斐波那契數列的通項公式

構造\(F_n = ax_1^n+bx_2^n\)

證實

\(F_{n+2} = ax_1^{n+2} + bx_2^{n+2} =a(x_1^nx_1^2) +b(x_2^nx_2^2)\)

由於\(x_1^2=x_1+1,x_2^2=x_1+1\)

因此\(F_{n+2}=ax_1^n(x_1+1)+bx_2^n(x_2+1) = ax_1^{n+1}+bx_2^{n+1}+ax_1^n+bx_2^n =F_{n+1}+F_n\)

因此\(F_n = ax_1^n+bx_2^n\)

已知\(F_1=1,F_2=1\),代入得

\(\left \{\begin{matrix} ax_1+bx_2=1 \\a^2x_1^2+b^2x_2^2=1 \end{matrix} \right .\)

解得 \(a=\frac{1}{\sqrt{5}},b=-\frac{1}{\sqrt{5}}\)

因此
\[ F_n=\frac{\sqrt{5}}{5}[(\frac{1+\sqrt{5}}{2})^n-(\frac{1-\sqrt{5}}{2})^n] \]

相關文章
相關標籤/搜索