部分简单数论模板(未完待续)

来源:互联网 发布:wordpress seo插件 编辑:程序博客网 时间:2024/06/15 11:44

1.著名的欧几里得算法

附上一些gcd和lcm的简单性质:
lcm(S/a, S/b) = S/gcd(a, b)

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

2.扩展欧几里得算法

int ex_gcd(int a,int b,int& x,int& y){    if(b==0){        x = 1, y = 0;        return a;    }    int d = ex_gcd(b,a%b,y,x);//注意x,y互换位置    y -= a/b*x;    return d; //返回值是a,b的GCD}

或者另一种实现

void ex_gcd(int a,int b,int &d,int &x,int &y){    if(b==0){        x = 1, y = 0, d = a;    }    else{        gcd(b,a%b,d,y,x);        y -= a/b*x;    }}

3.快速幂

long long pow(long long a,long long b){    long long ans = 1,base = a;    while(b){        if(b&1)            ans *= basebase *= base;        b>>=1;    }    return ans;}

4.快速幂取模

long long pow_mod(long long a,long long b,long long mod){    a %= mod;//视情况而定,如果a*a可能爆long long,就加上    long long ans = 1,temp = a;    while(b){        if(b&1)            ans = (ans*temp) % mod;        temp = temp*temp%mod;        b /= 2;    }    return ans;}

5.线性时间素数筛

const int maxn = 1e6+10;bool vis[maxn];int prime_cnt;int prim[maxn/10+5];void Prime(){    prime_cnt = 0;    memset(vis,0,sizeof(vis));    for(int i=2;i<maxn;i++){        if(vis[i]==0)            prim[prime_cnt++] = i;        for(int j=0;j<prime_cnt && i*prim[i]<maxn;j++){            vis[i*prim[i]] = 1;            if(i % prim[i] == 0)                break;        }    }}

6.区间素数筛

const int maxn = 1e5+10;bool vis[maxn];int prim[maxn];int cnt;void Prime(long long L,long long R){    long long len = R-L+1;    memset(vis,0,sizeof(vis));    int flag = 0;    if(L%2==1)        flag++;    for(int i=flag;i<len;i+=2)        vis[i] = 1;    int m = sqrt(R+0.5);    for(int i=3;i<=m;i+=2){        if(i>L && vis[i-L] == true)            continue;        int j= (L/i)*i;        if(j<L)            j+=i;        if(j==i)            j+=i;        j-=L;        for(;j<len;j+=i)            vis[j] = 1;    }    if(L==1)        vis[0] = true;    if(L<=2)        vis[2-L] = false;    cnt = 0;    for(int i=0;i<len;i++)        if(!vis[i])            prim[cnt++] = i+L;}

7.欧拉函数

//直接求法,复杂度O(sqrt(n))int phi(int n){    int ans = n,a = n;    for(int i=2;i*i<a;i++){        while(a%i==0){            ans = ans/i*(i-1);//先进行除法防止中间数据溢出            while(a%i==0)                a /= i;        }    }    if(a>1)        ans = ans/a*(a-1);    return ans;}

求区间内所有值的欧拉函数值,可以使用筛法

//欧拉筛int euler[maxn];void Euler(){    for(int i=2;i<maxn;i++)        euler[i] = i;    for(int i = 2;i<maxn;i++)        if(euler[i] == i)            for(int j=i;j<maxn;j+=i)                euler[j] = euler[j]/i*(i-1);}

8.逆元

扩展欧几里得求解逆元

int ex_gcd(int a,int b,int& x,int& y){    if(b==0){        x = 1,y = 0;        return a;    }    int d = ex_gcd(b,b%a,y,x);    y -= a/b*x;    return d;}int inv(int a,int p)//不存在则返回-1{    int d,x,y;    d = ex_gcd(a,p,x,y);    return d==1?(x%p+p)%p:-1;}

费马小定理求解逆元

int inv(int a,int p){    return pow_mod(a,p-2,p);//或许会用到降幂公式}

若模数p为素数,可根据逆元的性质求解:inv(a) = (p - p / a) * inv(p % a) % p

//求a关于p的逆元,注意:a要小于p,最好传参前先把a%p一下int inv(int a,int p){    return a==1 ? 1 : (p-p/a)*inv(p%a,p)%p;}

运用刚刚提到的性质,可以在O(n)的时间内求出1~n的逆元

const long long n = 1e5+10;const long long Mod = 1e9+7;long long inv[n];void Inv(){    inv[1] = 1;    for(int i=2;i<n;i++)        inv[i] = (Mod-Mod/i)*inv[Mod%i]%Mod;}

9.中国剩余定理
模数互素时

long long CRT(int n,long long a[],long long m[]){    long long M = 1,ans = 0;    for(int i=0;i<n;i++)        M *= m[i];    for(int i=0;i<n;i++){        long long temp = M/m[i];        ans = (ans+temp*inv(temp,m[i])*a[i])%M;    }    return (ans+M)%M;}

模数不互素时

typedef long long ll;typedef pair<ll, ll> pll;//求解A[i]x = B[i] (mod M[i]),总共n个线性方程组 pll CRT(int n, ll A[], ll B[], ll M[]) {    ll ans = 0, m = 1;    for(int i = 0; i < n; i ++) {        ll a = A[i] * m, b = B[i] - A[i]*ans, d = gcd(M[i], a);        if(b % d != 0)              return pll(0, -1);//答案不存在,返回-1         ll t = b/d * inv(a/d, M[i]/d)%(M[i]/d);        ans = ans + m*t;        m *= M[i]/d;    }    ans = (ans % m + m ) % m;    return pll(ans, m);//返回的x就是答案,m是最后的lcm值 }

10.组合数取模
杨辉三角求解,复杂度O(n2),可以用来求解1-1000范围内的组合数

const int N = 1000 + 5;const int MOD = 1e9 + 7;int comb[N][N]; //comb[n][m]就是C(n,m)void init(){    for(int i = 0; i < N; i ++){        comb[i][0] = comb[i][i] = 1;        for(int j = 1; j < i; j ++){            comb[i][j] = comb[i-1][j] + comb[i-1][j-1];            comb[i][j] %= MOD;        }    }}

逆元求解,复杂度O(n)

const int N = 200000 + 5;const int MOD = (int)1e9 + 7;int F[N], Finv[N], inv[N];//F是阶乘,Finv是逆元的阶乘 void init(){    inv[1] = 1;    for(int i = 2; i < N; i ++){        inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;    }    F[0] = Finv[0] = 1;    for(int i = 1; i < N; i ++){        F[i] = F[i-1] * 1ll * i % MOD;        Finv[i] = Finv[i-1] * 1ll * inv[i] % MOD;    }}int comb(int n, int m){//comb(n, m)就是C(n, m)     if(m < 0 || m > n) return 0;    return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD;}

大组合数取模,卢卡斯定理,用于n和m特别大而p<1e5的情况
C(n, m) % p = C(n / p, m / p) * C(n%p, m%p) % p

11.康托展开

//康托展开,把一个数字num展开成一个数组s,k是数组长度void cantor(int s[], LL num, int k){    int t;    bool h[k];//0到k-1,表示是否出现过     memset(h, 0, sizeof(h));     for(int i = 0; i < k; i ++){        t = num / fac[k-i-1];        num = num % fac[k-i-1];        for(int j = 0, pos = 0; ; j ++, pos ++){            if(h[pos]) j --;            if(j == t){                h[pos] = true;                s[i] = pos + 1;                break;            }        }    }}

以及,康托逆展开

//康托逆展开,把一个数组s换算成一个数字num void inv_cantor(int s[], LL &num, int k){    int cnt;    num = 0;    for(int i = 0; i < k; i ++){        cnt = 0;        for(int j = i + 1; j < k; j ++){            if(s[i] > s[j]) cnt ++;//判断几个数小于它        }        num += fac[k-i-1] * cnt;    }}

12.容斥原理
容斥原理有很多实现方式,可以选择DFS,队列数组以及位操作的方式来实现。
以HDU-4135为例分别贴上代码

//队列数组#include<cstdio>using namespace std;typedef long long ll;int cnt,Cnt;int N[100],que[100000];void work(ll n){    //预处理n的所有质因子    Cnt = 0;    for(int i=2;i*i<n;i++)        if(n%i==0){            N[Cnt++] = i;            while(n%i==0)                n/=i;        }    if(n>1)        N[Cnt++] = n;    //这里是关键,在队列数组的顺位置决定了正负    cnt = 0;    int l;    for(int i=0;i<Cnt;i++){        l = cnt;        for(int j=0;j<l;j++)            que[cnt++] = que[j]*N[i];        que[cnt++] = N[i];    }}ll solve(ll a){    ll ans = a,flag = -1;    for(int i=0;i<cnt;i++){        ans += flag* a/que[i];        flag = - flag;    }    return ans;}int main(){    int T;    scanf("%d",&T);    ll a,b,n;    for(int tt=1;tt<=T;tt++){        scanf("%I64d %I64d %I64d",&a,&b,&n);        work(n);        printf("Case #%d: %I64d\n",tt,solve(b)-solve(a-1));    }}
//位操作#include<cstdio>using namespace std;typedef long long LL;int Cnt;LL N[100];void init(LL x){    Cnt = 0;    for(int i=2;i*i<x;i++)        if(x%i==0){            N[Cnt++] = i;            while(x%i==0)                x/=i;        }    if(x>1)        N[Cnt++] = x;}LL work(LL x){    //接下来容斥    LL ans = x, cnt, temp;    for(int i = 1; i < (1 << Cnt); i ++){        cnt = 0;        temp = 1;        for(int j = 0; j < Cnt; j ++){            if(i & (1 << j)){                temp *= N[j];                cnt ++;            }        }        if(cnt & 1) ans -= x / temp;        else ans += x / temp;    }    return ans;}int main(){    int T;    LL l, r, n;    scanf("%d", &T);    for(int cas = 1; cas <= T; cas ++){        scanf("%I64d%I64d%I64d", &l, &r, &n);        init(n);        printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));    }}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 新建房屋一面墙体有裂缝怎么办 卫生间地砖缝隙出现渗水怎么办 西户窗户太晒怎么办 西晒的墙面很烫怎么办 儿童房颜色太粉了怎么办? 小孩在家里偷钱怎么办 脾气不好的猫该怎么办 二年孩子偷钱怎么办 孩子偷钱2000报警怎么办? 我儿子十岁老是偷钱怎么办 13孩子偷同学钱怎么办 孩子偷同学的钱怎么办 儿子十四岁了老偷钱怎么办 发现初中生的儿子偷钱怎么办 被亲戚怀疑儿子偷钱怎么办 房门选的太白了怎么办 大厅地砖颜色比墙砖浅怎么办 房屋外墙渗水物业不管怎么办 走丢了怎么办教学反思 托班教案迷路了怎么办 大班安全教案遇到小偷怎么办 小班孩子舞台表演找不到位置怎么办 懂你英语学完了怎么办 小班社会走丢了怎么办 帮小老鼠搬鸡蛋怎么办 小老鼠还能怎么办鸡蛋 中班教案走丢了怎么办 走丢了怎么办可后反思 社会走丢了怎么办教案 孩子一直不吃幼儿园的饭怎么办 大班安全游泳抽筋怎么办反思 汤洒了怎么办教学反思 迷路了怎么办小班详案 大班牙又痛又摇怎么办 大班安全教案着火了怎么办 小班社会生病了怎么办反思 脚扭伤了怎么办的反思 中班安全迷了眼怎么办 15个月的小孩长泡疹怎么办 墨盒加错颜色墨水怎么办 墨盒颜色加错了怎么办