dls的数论-整除,gcd

数论

整除/gcd

一些常见的结论

1-n之间的素数个数:n/lnn 级别的
第n个素数的大小:nlogn级别大小
1-n的倒数和:logn级别
1-n之间素数的倒数和:loglogn级别的
a|c, b|c, (a, b) = 1 --> ab|c,  a,b分别是c的一些质因子乘积,且a,b没有相同的质因子,所以c%(ab)==0
                                或者:[a, b]|c. 其中[a,b] = ab|(a, b) = ab
a|bc, (a,b) = 1  ---> a|c 
p|ab ---> p|a或p|b
所有公倍数都是最小公倍数的倍数
所有公约数都是最大公约数的约数
a = p1^a1*p2^a2....pk^ak
b = p1^b1*p2^b2....pk^bk
(a, b) = pi^min(ai, bi) 的连乘积
[a, b] = pi^max(ai, bi) 的连乘积
(a, b)[a, b] = ab = pi^(min(ai, bi) + max(ai, bi)) 的连乘积

欧几里得

简单证明
(a, b) = (a - b, b)
d|a, d|b ---> d|(a-b), d|b
d|(a, b) <--> d|(a-b, b)
时间复杂度:log(min(a, b))
(a, b) = (b, a mod b) 分类讨论可以发现每做一次都有一个数减小至少一半
n个数依次做欧几里得,时间复杂度是n+log(max(ai)),不是nlogn,因为有个全局的最小公约数
最小公倍数推荐写法:(a/gcd(a, b))*b
另一种求公约数的方法
如果a,b都是奇数,那么(a, b) = (a-b, b)
如果a是偶数,b是奇数,那么(a, b) = (a/2, b)
如果a,b都是偶数,(a, b) = 2(a/2, b/2)
主要用于高精度取余
int128等也比较快

裴蜀定理&扩展欧几里得

a, b, (a, b)|d  <--> au+bv=d 存在整数解
左推右构造证明:
类似于(a, b)不断->(b, a%b)
其实就是扩展欧几里德
ax+by=d --> (b*(a/b) + a mod b)x + by = d --> b(a/b*x+y)+(amodb)x = d 除法都是向下取整
系数的变化从(a, b) --> (b, a mod b)
所以我们可以倒退回去根据现在的x,y求原来的x,y
该方法求出来的x<b,y<a (可以判断解的范围) 并且求出来的解是最小的,但是不一定是正数
x = y1
y = x1 - a/b*y1   其中x,y是上一层的,x1,y1是下一层的
当d不是(a, b)的时候,ax + by = d 先求求解ax0 + by0 = (a, b)
令x1=x0*d/(a, b), y1=y0*d/(a, b)就是一组解了
通解:xk = x1 + b/(a, b)*k, yk = y1 - a/(a, b)*k
// 扩展欧几里得1
// 求解ax-by=gcd(a,b)的最小非负整数解 a,b都是正数
// 题目链接:http://oj.daimayuan.top/course/12/problem/487

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long LL;

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

int main(){
    int T;
    cin >> T;
    while(T--){
        int a, b;
        scanf("%d %d", &a, &b);
        // 可以证明的是x,y的范围是不会超过a,b的,所以不需要开范围更大的数据类型
        int x, y;
        int d = exgcd(a, b, x, y);
        y = -y;

        // 暴力调整
        // 因为是求解ax-by=gcd(a,b),所以调整的时候x,y相同的符号
        // while(x < 0 || y < 0) x += b/d, y += a/d;
        // while(x >= b/d && y >= a/d) x -= b/d, y -= a/d;

        // 其实x,y都是正数的话扩展欧几里得可以保证现在的x,y就是最小的解
        // 如果不是的话,只需要调整一次就可以了
        if(x < 0 || y < 0) x += b/d, y += a/d;
        printf("%d %d\n", x, y);

    }
    return 0;
}

// 扩展欧几里得2
// 求解ax+by=d的非负整数解,如果有解的话输出x最小的解,否则输出-1,a,b>0
// 题目链接:http://oj.daimayuan.top/course/12/problem/488

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long LL;

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

int main(){
    int T;
    cin >> T;
    while(T--){
        int a, b;
        LL d;
        scanf("%d %d %lld", &a, &b, &d);
        int x, y;
        int t = exgcd(a, b, x, y);
        if(d % t){
            puts("-1");
            continue;
        }
        a = a / t;
        b = b / t;
        d = d / t;
        // 因为d的范围是1e18的,所以这里可能会爆longlong
        // __int128 xx = (__int128) x * d;
        // xx %= b;
        // if(xx < 0) xx += b;
        // __int128 yy = (d - a * xx) / b;
        // if(yy < 0) puts("-1");
        // else printf("%lld %lld\n", (LL)xx, (LL)yy);
        // 也可以先对d取mod这样就不会爆longlong了
        LL xx = (LL)x * (d % b) % b;
        if(xx < 0) xx += b;
        LL yy = (d - a * xx) / b;
        if(yy < 0) puts("-1");
        else printf("%lld %lld\n", xx, yy);       
    }
    return 0;
}

posted @ 2022-03-06 21:20  牛佳文  阅读(162)  评论(0编辑  收藏  举报