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

本文详细介绍了多种计算机科学领域的经典算法,包括欧几里得算法、扩展欧几里得算法、快速幂算法及其取模变种、线性和区间素数筛算法、欧拉函数计算方法、逆元求解、中国剩余定理的应用、组合数取模的不同方法、康托展开及逆展开、以及容斥原理的几种实现方式等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值