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;
        }
    }
}

例题QwQ、Problem A. 最佳进制

快速幂

// 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值