数论基础 扩展欧几里得 线性筛 逆元 欧拉函数 Lucas定理

扩展欧几里得

欧几里得算法,又叫辗转相除法,用于求两个数的最大公约数(gcd)。

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

由此可以得到最小公倍数 lcm(a,b)=a/gcd(a,b)b (先除防止溢出)。
扩展欧几里得,是用于解出方程 ax+by=gcd(a,b) 的一组解的。比如方程 6x+15y=3 ,可以解出 (3,1) ,当然也有其它解。事实上,若有一组解 (x0,y0) ,则有任意解 (x0+kb/gcd(a,b),y0ka/gcd(a,b))
知道这个有什么用呢?可以用来求任意不定方程的解。若有 ax+by=gcd(a,b) 的一组解 (x,y) ,则方程 ax+by=c 的一组解是 (xc/gcd(a,b),yc/gcd(a,b))

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

扩展欧几里得可以用于求不定方程,也是一些其他算法的基础。

线性筛

lrj书上写了一种筛法,叫Eratosthenes筛。
曾经我以为那就是线性筛。直到我看到我的代码跑了3500ms而别人只跑了1400ms。
线性筛,或是欧拉筛,时间复杂度是线性的。
为什么呢?因为每个数只会被筛去一次。Eratosthenes筛每个数可能筛多次,但线性筛每次都只用最小的质数去筛,因此只会筛一次。
我们来对比一下两种代码。
这是Eratosthenes筛,它的复杂度是 O(nlog2n)

//vis=1表示合数
for(int i = 2; i <= n; i++) if(!vis[i]) 
    for(int j = i*i; j <= n; j += i) vis[j] = 1;

这是线性筛。

//vis=1表示合数
for(int i = 2; i <= n; i++) {
    if(!vis[i]) prime[primen++] = i;
    for(int j = 0; j < primen; j++) {
        if(i*prime[j] > n) break;
        vis[i*prime[j]] = 1;
        if(i % prime[j] == 0) break; //这句话保证只筛一次
    }
}

这样就很显然了吧。

线性筛欧拉函数

欧拉函数 phi(x) 等于不超过 x 且与x互质的数的个数。比如,质数的欧拉函数是它减一。
欧拉函数是积性函数。一个函数是积性函数,就是说如果 x=ij ,那么 f(x)=f(i)f(j) 。利用这个性质,就可以用线性筛筛它。
在上面的代码做一点修改就可以了。

for(int i = 2; i <= n; i++) {
    if(!vis[i]) {    //i是质数
        prime[primen++] = i;
        phi[i] = i - 1;  //质数的欧拉函数是比它少一
    }
    for(int j = 0; j < primen; j++) {
        if(i*prime[j] > n) break;
        vis[i*prime[j]] = 1;
        if(i % prime[j] != 0) 
            //积性函数
            phi[i*prime[j]] = phi[i] * phi[prime[j]];
        else{
            //若x是质数p的倍数,则phi[x]=phi[x/p]*p
            phi[i*prime[j]] = phi[i] * prime[j];
            break; //保证只筛一次
        }
    }
}

线性筛莫比乌斯函数

莫比乌斯函数有三个性质:
- 质数的莫比乌斯函数是-1。
- 完全平方数的莫比乌斯函数是0。
- 莫比乌斯函数是积性函数。若 x=ip (p是质数),则 μ(x)=μ(i)
筛筛筛。

for(int i = 2; i <= n; i++) {
    if(!vis[i]) {    
        prime[primen++] = i;
        phi[i] = i - 1; 
        mu[i] = -1;//质数的莫比乌斯函数是-1
    }
    for(int j = 0; j < primen; j++) {
        if(i*prime[j] > n) break;
        vis[i*prime[j]] = 1;
        if(i % prime[j] != 0) {
            phi[i*prime[j]] = phi[i] * phi[prime[j]];
            mu[i*prime[j]] = -mu[i];
        }else{
            phi[i*prime[j]] = phi[i] * prime[j];
            //prime[j]的倍数又乘上prime[j],肯定是完全平方数
            mu[i*prime[j]] = 0;
            break; 
        }
    }
}

逆元

什么是逆元?
在一般情况下,一个数除以另一个数等于乘它的倒数。
在模意义下,一个数除以另一个数等于乘它的逆元
当然,被除数是能被除数整除的。
定义式: ax1(modn) ,即 a 1x(modn)
a x是模 n 意义下的逆,则有ax与1同余,即ax+ny=1。y是任意整数。
这不就是扩展欧几里得吗。
复杂度 O(nlogp)

int inv(int a, int n) {
    int d, x, y;  //变量名意义同上
    exgcd(a, n, d, x, y);
    return d == 1 ? (x+n)%n : -1;
    //d!=1则逆不存在。结果可能小于0,因此要模n。
}

当n是质数时,可以用费马小定理。简略地说(其实是我不会证), inv(a,n)=pow(a,n2,n) 。其中pow是幂取模,用快速幂,复杂度是 O(nlogp)
然而和扩欧是一样的。这里就不贴代码了。

线性求所有逆元

n是模数。取k、r,使 n=ki+r 成立。其中 r<i
那么显然有 k=n/i r=nmodi
化简式子得 ki+r0 ,继而有 i 1kr 1
于是, inv(i)=n/iinv(nmodi)
就可以线性递推了。
编程的时候怎么处理负号?把 n/i 改成 nn/i 就好了。

inv[1] = 1;
for(int i = 2; i <= n; i++) inv[i] = (ll)(p-p/i) * inv[p%i] % p;

用逆元求模意义组合数

非常不好,因为我没时间了。但东西还是得写完。
以下fac[]表示阶乘,可以O(n)预处理。很容易理解,把组合数计算式中的除法改成乘以逆元就好了。逆元可以用上述三种方法求。
C(n,m)=fac[n]inv(fac[m]fac[nm]modp,p)modp;

Lucas定理

定理内容:

C(n,m)C(n/p,m/p)C(nmodp,mmodp)

其中第一部分继续递归,而第二部分直接计算。

int comb(int n, int m, int p) { //Lucas
    int ans = 1;
    while(n && m) {
        long long nn = n%p, mm = m%p;
        if(nn < mm) return 0; //防止无限递归
        ans = ans * fac[nn] * inv(fac[mm]*fac[nn-mm]%p, p) % p;
        n /= p; m /= p;
    }
    return ans;
}    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值