1. 程式人生 > >NOIP數學複習

NOIP數學複習

NOIP中的數學相關知識

素數及其相關

a.判斷素數 複雜度O(根號n)

bool prime(int x) {
    if(x == 0 || x == 1) return false;
    for(int i = 2; i * i <= x; i++) {
        if(x % i == 0) return false;
    }
    return true;

b.篩素數

埃氏篩法 複雜度O(n loglog n)

對於每個數p,會劃掉p/n個數

#include<iostream>
#include<cstdio>
using namespace std; const int SIZE=1e7; int prime[SIZE];// 第i個素數 bool is_prime[SIZE];//true表示i是素數 int slove(int n) { int p = 0; for(int i = 0; i <= n; i++) is_prime[i] = true;//初始化 is_prime[0] = is_prime[1] = false;//0,1不是素數 for(int i = 2; i <= n; i++) { if(is_prime[i])//zkzk
{ prime[p++] = i;//計算素數的個數,也記錄下了素數 for(int j = 2 * i; j <= n; j += i)// 除掉了i的倍數的數字 is_prime[j] = false; } } return p; } int main() { int n; while(cin >> n) { int res = slove(n); cout << res << endl;//素數個數
for(int i = 0; i < res; i++)//列出素數 cout << prime[i] << endl; } } //zk:初始最小素數是2,將資料內2的所有倍數扔掉。此時3不能被更小的數整出,即為素數依次類推;

尤拉篩法 複雜度O(n)

luogu 線篩模板:

#include <cstdio>
using namespace std;
const int maxn=10000000;
int n,m;
int prime[maxn],flag[maxn];
int tot;
void get_prime(int n){
    flag[0]=flag[1]=1;//不是素數 
    for (int i=2; i<=n; ++i)
    {
        if (!flag[i]) prime[++tot]=i;
        for (int j=1; j<=tot&&i*prime[j]<=n; ++j)
        {
            flag[i*prime[j]]=1;
            if (i%prime[j]==0) break;//prime[j]必定是prime[j]*i的最小因子;i中有因子prime[j],已被篩到一次 就退出找下一個i 
        }
    }
}
int main(){
    scanf("%d%d", &n, &m);
    get_prime(n);
    int t;
    for (int i=1; i<=m; ++i)
    {
        scanf("%d", &t);
        if (!flag[t]) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

2.區間篩素數

(poj2689)
篩[a,b)中的素數
因為b以內合數的最小質因數一定不超過sqrt(b),先分別做好[2,sqrt(b))的表和[a,b)的表,然後從[2,sqrt(b))的表中篩得素數的同時,也將其倍數從[a,b)的表中劃去,最後剩下的就是區間[a,b)內的素數。
用埃氏篩的原理:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;
bool is_prime[maxn],is_p[maxn];//is_prime[]表示le~ri中下邊位移後的素數判定(a位移1),is_p[]表示1~根號n中的素數判定 
ll prime[maxn],sum;
void get_prime(ll le,ll ri){
    for(ll i=2;i*i<ri;i++) is_p[i]=true;//對[2,sqrt(b))的初始化全為質數,i要從2開始, 1不是素數 
    for(ll i=1;i<=ri-le;i++)    is_prime[i]=true;//對下標偏移後的[a,b)進行初始化,i要從1開始 
    for(ll i=2;i*i<=ri;i++){
        if(is_p[i])for(ll j=2*i;j*j<=ri;j+=i) is_p[j]=false;//篩選素數i的倍數 
        for(ll j=max(2LL,(le+i-1)/i) * i;j<=ri;j+=i) is_prime[j-le]=false;//(a+i-1)/i得到最接近a的i的倍數,最低是i的2倍,然後篩選
    }
    for(ll i=1;i<ri-le;i++) if(is_prime[i]) prime[++sum]=i+le;
}
int main(){
    ll l,r;
    while(scanf("%lld%lld",&l,&r)){
        sum=0;  memset(prime,0,sizeof(prime));
        get_prime(l,r);
        printf("%lld\n",sum);
    }
    return 0;
}

3.分解質因數

基於唯一分解定理
n = P1^a1 * P2^a2 * …………* Pn^an(P1 < P2 < ……Pn),Pi為質數;
樸素法(可以先求出素數來優化一下)

void fj(int x) {
    for(int i = 2; i * i <= x && x > 1; i++) {
        while(x % i == 0) {
            if(!check[i]) zhi[++cnt] = i;//check判斷有沒有出現過
            check[i]++;
            x /= i;
            if(x == 1) break;
        }
    }
}

快速冪

// a ^ b % mod
int ksm(int a,int b,int mod) {
    int ans=1;
    a%=mod
    while(b){
        if(b&1)ans=(ans*a)%mod;
        b>>=1;
        a=(a*a)%mod
    }
    return ans;
}

排列組合

這裡寫圖片描述
這裡寫圖片描述

組合

void init(){
    f[0][0]=1;
    for(int i = 1; i <= inf; ++i){
        f[i][0]=1;
        for(int j = 1;j <= i; ++j){
            f[i][j] = f[i-1][j] + f[i-1][j-1];
        }
    }
}
next_permutation(a +1,a+1+n); STL全排列

同餘

同餘的性質

這裡寫圖片描述

0.0

這裡寫圖片描述
同餘方程

歐幾里得演算法

好久之前的筆記——關於gcd,exgcd,cal

/*設兩數為a、b(a>b),求a和b最大公約數(a,b)的步驟如下:
用a除以b,得a÷b=q……r1(0≤r1)。
若r1=0,則(a,b)=b;若r1≠0,則再用b除以r1,得b÷r1=q……r2 (0≤r2).
若r2=0,則(a,b)=r1,若r2≠0,則繼續用r1除以r2,……
如此下去,直到能整除為止。其最後一個非零除數即為(a,b)。*/
//—————————————————————————————————-

#include<stdio.h>
#define ll long long 
ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}

int main(){
    ll a,b;
    while(scanf("%lld%lld",&a,&b)!=EOF)
    {
        printf("%lld\n",gcd(a,b));//事實上如果 a 小於 b,那第一次就會先交換 a 與 b。
    }
    return 0;
}
//--------------------------------------------------------
void exgcd(ll a,ll b,ll& d,ll& x,ll& y){
    if(!b){d=a;x=1;y=0;}
    else {exgcd(b,a%b,d,y,x);y-=x*(a/b);}
}

//逆元

ll cal(ll a,ll m)
{
    ll d,x,y;
    exgcd(a,m,d,x,y);
    return (x%m+m)%m;
} 

int main(){
    ll a,b,d,x,y;
    while(scanf("%lld%lld",&a,&b)!=EOF){
        exgcd(a,b,d,x,y);
        printf("%lld*%lld+%lld*%lld=%lld\n",a,x,b,y,d);
    }
    return 0;
}

/*
同餘方程
先講一下擴充套件歐幾里德定律:

對於不完全為0的非負整數a,b,gcd(a, b)表示a, b的最大公約數,必定存在整數對x,y,滿足a*x+b*y==gcd(a, b)。

證明:(轉)

a*x1+b*y1=gcd(a, b);

b*x2+(a%b)*y2=gcd(b, a%b);

因為由歐幾里德定理知:gcd(a, b)==gcd(b, a%b)

所以a*x1+b*y1=b*x2+(a%b)*y2; 因為r=a%b, r =a-k*b所以==>

a*x1+b*y1=b*x2+(a-k*b)*y2; 因為k=a/b;所以==>

a*x1+b*y1=b*x2+(a-(a/b)*b)*y2; 展開得到==>

a*x1+b*y1=b*x2+a*y2-b*(a/b)*y2;轉換得到 ==>

a*x1+b*y1=a*y2+b*(x2-(a/b)*y2);

觀察上式可知 x1=y2, y1=x2-a/b*y2;

由此可知x1,y1是由x2,y2得出來的,由此類推x2,y2是由x3,y3得出來的,

那什麼時候是終止呢?也就是遞迴gcd(a, b)中b=0時;也就是說此時a的值就是要求得最大公約數

即gcd(a, 0)此時由擴充套件歐幾里得定律a*x+b*y==gcd(a, b)

知 a*x+b*y=a;

解出x=1, y=0;

此時就是遞迴終止的地方:

——————–分界線————————

那麼問題來了,這破東西有啥用呢

問的好,它可以用來求一個同餘方程的解,也就是逆元

ax ≡ 1 (mod b),現在找x能夠使這個式子成立

這個式子等價於ax+by=1

這不就是拓展歐幾里得的表示式嗎,從這裡我們可以看出如果gcd(a,b)!=1那麼這個方程就不會有解

所以說呢

形如a*x + b*y = c這樣的式子,若c%gcd(a,b)==1,那麼這個方程就會有解

PS:題目保證有解,所以不用判斷了

但是一般題目裡會讓你求一個最小的x,當你用拓歐求出一個解時,一般會讓你去找一個最小解,我們只需要對這個數取模b就行了(如果求正數,你只需要先加一個b,再取模行了,應該都知道吧)

程式碼如下

#include <cstdio>
using namespace std;
typedef ll ll;
ll a,b,x,y;
ll e_gcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    ll ans=e_gcd(b,a%b,x,y);
    ll tmp=x;
    x=y;
    y=tmp-(a/b)*y;
    return ans;
} 
int main()
{
    scanf("%lld%lld",&a,&b);
    e_gcd(a,b,x,y);
    printf("%lld",(x+b)%b);
    return 0;
}

1.最大公因數

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

2.最小公倍數

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

霍dalao的醜字:
這裡寫圖片描述

擴充套件歐幾里得演算法

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

擴歐求解不定方程:
這裡寫圖片描述

逆元

1.擴歐

這裡寫圖片描述

int inv(int a, int n) {
    int d,x,y;
    exgcd(a,n,d,x,y);
    return (x % n + n) % n;
}

2.費馬小定理

a^(p-1) ≡1 (mod p)

int inv(int a, int n) {
    return ksm(a,n-2,n)
}

遞推規律

1.Catalan數

這裡寫圖片描述

h(n)=(4n-2)/(n+1)*h(n-1)(n>1) h(0)=1 
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2) 
h(n)=C(2n,n)/(n+1)

2.錯排公式

十本不同的書放在書架上。現重新擺放,使每本書都不在原來放的位置。有幾種擺法?
f[1] = 0, f[2] = 1
f[i]=(i-1)*(f[n-1]+f[n-2])

3.斐波那契數列

f[1] = 1, f[2] = 1;
f[i] = f[i - 1] + f[i - 2]

5.除數函式

設 d(n)為 n 的所有因數的個數,由乘法原理可知,
d(n)=(a1 +1)(a2 +1)…(ak +1)。

4.秦九韶演算法

這裡寫圖片描述

int qing(int x){
    int ans=a[n];
    for(int i=n-1;i>=1;i--)
        ans=ans*x+a[i];
    return ans;
}