个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

数论小结

分解质因数

#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;

int main() {
    int n;
    cin >> n;
    while(n--) {
        int x;
        cin >> x;
        for(int i = 2; i <= sqrt(x);i++) {
            if(x % i == 0) {
                int s = 0;
                while(x % i == 0) {
                    x /= i;
                    s++;
                }
                cout << i << " " << s << endl; // 数量
            }
        }
        if(x > 1)   cout << x << " " << 1 << endl;
        cout << endl;
    }

    return 0;
}

约数个数

QQ浏览器截图20200731215214.png

约数之和

基本思想

如果\(N = p_1^{c_1} * p_2^{c_2} * ...p_k^{c_k}\)

约束个数 \((c1 + 1)(c2+1)(c3+1)...(ck+1)\)

约数之和 \((p1^0 + p1^1 + ...+ p1 ^ c_1) *...*(pk^0 + pk^1 + ...+pk^{c_k})\)
while (b--) t = (t * a + 1) % mod;

t = t * p + 1

t = 1

t = p + 1

\(t = p ^ 2 + p + 1\)

\(t = p ^ 3 + p ^ 2 + p + 1\)

......

#include<iostream>
#include<unordered_map>
typedef long long ll;
using namespace std;
const int mod = 1e9 + 7;
int n;
int main() {
    scanf("%d", &n);
    unordered_map<int,int> primes;
    while(n--) {
        int x;
        scanf("%d", &x);
        
        for(int i = 2;i * i <= x;i++) {
            while(x % i == 0) {
                x /= i;
                primes[i]++;
            }
        }
        if(x > 1)   primes[x]++;
    }
    ll ans = 1;
    for(auto p : primes) {
       ll a = p.first, b = p.second;
       ll t = 1;
       while(b--)   t = (t * a + 1) % mod; // 求单个约s的乘数
       ans = ans * t % mod; // 约束之和计算
    }
    cout << ans << endl;
    return 0;
}

欧拉函数

image-20211214153754113

image-20211217195401737

欧拉定理:$a ^{\phi(n)} \equiv 1 (modn) $(a、n互质)

1~n中,\(a_1、a_2、..a_\phi(n)\) 是和n互质的数,那么\(aa_1、aa_2、....aa_\phi(n)\)也和n互质,

\(aa_i\equiv aa_j(mod n)\)

扩展欧几里得算法

\(ax + by = gcd(a,b)\)

bx1 + (a%b)*y1 = gcd(b,a%b) = gcd(a,b) .....,gcd的值一直是相等的

a % b = a - (a / b) * b,代入

\(bx1 + a * y1 - (a/b) * b* y1\)

\(= a * y1 + b1 * (x1 - a/b * y1) = ax + by\)

发现,\(x = y1\)\(y = (x_1 - a /b * y_1)\) (x,y是初始的,x1,y1是一步一步gcd变换的)

#include<iostream>
using namespace std;

void exgcd(int a,int b, int& x,int& y) {
    if(b == 0) {
        x = 1, y = 0;
        return;
    }
    exgcd(b, a % b, x, y);
    int t = y;
    y = x - (a / b) * y;
    x = t;
} 
// 第2z
void 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); // x放到y的位置那么递归里面y的改变就是在改变x 即x=y1 
    y -= a / b * x; // y = x1 - a/b*y1 // 因为x和调换了
    return d;
}

int main() {
    int n;
    cin >> n;
    while(n--) {
        int a, b, x, y;
        scanf("%d%d", &a, &b);
        exgcd(a, b, x, y);
        cout << x << " " << y << endl;
    }
    return 0;
}

快速幂求逆元

逆元的定义

费马小定理:如果p是一个质数,而整数a 不是 p 的倍数,则有 \(a^{p-1}\equiv 1(mod p)\)

乘法逆元:若整数b, m 互质,并且对于任意的整数 a, 如果满足b|a,则存在一个整数 x, 使得 \(a/b \equiv a * x(mod m)\), 称 x 为 b 的模 m 的乘法逆元,记为 \(b^{-1}(mod m)\)

\(a/b = a * x(mod m)\) \(a/b=a * b^{-1} (mod m)\) \(1 = b^{-1}*b (mod m)\)

根据费马小定理 \(b^{m-1} \equiv 1 (mod m)\)

\(b^{-1} * b \equiv 1(mod m)\)

\(b^{m-1} = b ^ {-1} * b\)\(b^{-1} = b ^ {m-2}\)

当b与m互质时, b 的乘法逆元为 \(b^{m-2}\)

当b 为 m 的倍数时, b 的逆元不存在

#include<iostream>
using namespace std;
typedef long long LL;

LL get_pow(int a, int b, int p) {
    LL ans = 1;
    while(b) {
        if(b & 1) {
            ans =(LL)ans * a % p; // 注意LL
        }
        a =(LL)a * a % p; // 注意LL类型 模上p之后还是int范围内
        b >>= 1;
    }
    return ans;
}

int main() {
    int n;
    scanf("%d", &n);
    while(n--) {
        int a, p;
        scanf("%d%d", &a, &p);
        if(a % p == 0)  puts("impossible");
        else    cout << get_pow(a,p - 2, p) << endl;
    }
    return 0;
}

求组合数

递推法

#include<iostream>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
LL f[2010][2010];
int main() {
    // 预处理
    for(int i = 0; i <= 2000; i++)
        for(int j = 0; j <= i;j++) { // 注意j <= i
            if(j == 0)  f[i][j] = 1;
            else    f[i][j] = (f[i-1][j-1] + f[i-1][j]) % mod; 
        }
    int n;
    cin >> n;
    while(n--) {
        int a, b;
        cin >> a >> b;
        cout << f[a][b] << endl;
    }
    return 0;
}

// f[0][0] = 1   f[1][1]=f[0][1]+f[0][0]=1  f[2][2]=f[1][2]+f[1][1]=1....

逆元法 \(1\leq b\leq a \leq 10^5\)

#include<iostream>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];

int get_pow(int a, int b, int mod) {
    int ans = 1;
    while(b) {
        if(b & 1)   ans = (LL)ans * a % mod; // 注意这里要加上LL
        a = (LL)a * a % mod; // 加上LL
        b >>= 1;
    }
    return ans;
}

int main() {
    fact[0] = infact[0] = 1; // 阶乘、逆元得阶乘
    for(int i = 1; i < N;i++) {
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * get_pow(i, mod - 2, mod) % mod;
    }
    int n;
    cin >> n;
    while(n--) {
        int a, b;
        scanf("%d%d", &a,&b);
        printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
    }
    
    return 0;
}

高精度+筛素数 组合数

image-20211217192436003

思路: 通过计算\(a!\)\(b!\)\((a-b)!\)中不同素数 p 的次数,用分子的次数减去分母的次数就是该素数的总次数,最后用高精度乘法计算结果即可。

比如说\(8! = 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1\),8 / 2 = 4,即2、4、6、8这几个是2的倍数, 8 / 4 = 2,即4、8是4的倍数,8 / 8 = 1,8是8的倍数。但是\(p^3\)也是\(p^2\)的倍数,\(p^2\) 也是p的倍数,即2算了1次,4算了两次,8算了3次。

比如 8 / 3 = 2,说明3、6是3的倍数,8 / 9 = 0, 3的平方大于8了,质数3总共出现2次。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 5010;
int primes[N], cnt;
int sum[N];
bool st[N];

// 筛素数
void get_primes(int n) {
    for(int i = 2; i <= n;i++) {
        if(!st[i])  primes[cnt++] = i;
        for(int j = 0; primes[j] * i <= n;j++) {
            st[primes[j] * i] = 1;
            if(i % primes[j] == 0)  break;
        }
    }
} 

// 求n!包含质因子p的个数
int get(int n, int p) {
    int res = 0;
    while(n) {
        res += n/p;
        n /= p;
    }
    return res;
}

// 高精度乘
vector<int> mul(vector<int> a, int b) {
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size(); i++) {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while(t) {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}

int main() {
    int a, b;
    cin >> a >> b;
    get_primes(a);
    for(int i = 0;i < cnt;i++) {
        int p = primes[i]; // 素数
        sum[i] = get(a,p) - get(b,p) - get(a-b,p); // 这个素数对应的次数(分子减去分母的)
    }
    vector<int> res;
    res.push_back(1);

    for(int i = 0; i < cnt;i++)
        for(int j = 0; j < sum[i]; j++) // primes[i]的次数
            res = mul(res, primes[i]);
    for(int i = res.size() - 1; i >= 0;i--) cout << res[i];
    puts("");
    return 0;
}

中国剩余定理

题目 整数的奇怪方式

m1、m2、,.....mk两两互质, \(x \equiv a_1(mod m_1)\) \(x \equiv a_2(modm_2)\) .....\(x \equiv a_k(mod m_k)\)

求解:\(M=m_1m_2....m_k\)\(M_i = \frac{M}{m_i}\)

\(x = a_1 * M_1 * M_1^{-1} + a_2*M_2 * M_2^{-1} +...a_kM_kM_k^{-1}\)

其中\(M_i^{-1}\)\(M_i\)\(m_i\)的逆

表达整数的奇怪方式

思路:通过两个等式合并成为一个等式,然后继续与下一个等式合并

  • 有解等价于 \((a_1,a_2) | (m_2,m_1)\) 其中\(()\)表示最大公约数, \([]\) 表示最小公倍数
  • \(k_1 + k (a2/d)\)\(k_2 + k(a_1 / d)\)是方程组的通解,求最小的\(k_1\) ,如果通过 exgcd求出了\(k_1\) ,\(k_1 % t+ t\)是最小的k1 (即k取0)。
  • 合并之后的方程 \(x = ka + x_0\) , \(x_0\)就是 \(a_1k_1 + m_1\)

image-20211217214446109

c++中的余数可能为负数:比如 \(-5 mod 3 = -2\) ,但在数学中余数是正的余数为1。

#include<iostream>
#include<cmath>
using namespace std;
typedef long long LL;
int n;

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



int main() {
    int n;
    bool flag = true;
    cin >> n;
    LL a1, m1;
    cin >> a1 >> m1;
    for(int i = 1; i < n;i++) {
        LL a2, m2, k1, k2;
        cin >> a2 >> m2;
        LL d = exgcd(a1, a2, k1, k2);
        if((m2 - m1) % d) {
            flag = false;
            break;
        }
        k1 = k1 * (m2 - m1) / d;
        LL t = abs(a2 / d); 
        k1 = (k1 % t + t) % t; // 最小的k1
        m1 = k1 * a1 + m1;
        a1 = (a1 / d * a2); // abs(a1 /d * a2) 可加可不加
    }
    if(!flag)   puts("-1");
    else    cout << (m1 % a1 + a1) % a1 << endl; // 用m1 = k1 * a1 + m1,求出右边的m1c
    return 0;
}

公约数

给定两个正整数 a 和 b,你需要回答q 个询问,每个询问给定两个整数l,r,你需要找到最大的整数x,满足

1、x 是 a 和 b 的公约数

2、l <= x <= r

思路: a b 的公约数一定是其最大公约数的约数

求其最大公约数的约数即可,将其排序。使用二分查找是否存在这个约数

// 求这个数的约数: a b 的公约数一定是最大公因数的约数
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1000010;
int cnt;
int yue[N]; // 存约数

int gcd(int a, int b) {
    return b ? gcd(b, a%b): a;
}
// 求约数
void divide(int x) {
    yue[cnt++] = 1;
    for(int i = 2; i * i <= x;i++) {
        if(x % i == 0) {
            yue[cnt++] = i;
            if(i * i != x)  yue[cnt++] = x / i;
        }
    }    
    yue[cnt++] = x;
}
int main() {
    int a, b;
    scanf("%d%d", &a, &b);
    int t = gcd(a, b);
    divide(t);
    sort(yue, yue + cnt);
    int q;
    scanf("%d", &q);
    while(q--) {
        int l, r;
        scanf("%d%d", &l, &r); // 范围区间
        // 二分找公约数
        int ll = 0, rr = cnt - 1;
        if(r < 1 || l > yue[rr]) {
            cout << "-1" << endl;
            continue;
        }
        while(ll < rr) {
            int mid = ll + rr + 1>> 1; // 注意要加上1 因为l = mid + 1
            if(yue[mid] < l) ll = mid + 1;
            else if (yue[mid] >= l && yue[mid] <= r)    ll = mid; // 找的是[l,r]间最大的数
            else rr = mid - 1;
        }
        if(yue[ll] < l || yue[ll] > r)  puts("-1");
        else cout << yue[ll]<<endl;
    }
    return 0;
}

辗转相减法

posted @ 2022-02-15 23:22  红叶~  阅读(25)  评论(0编辑  收藏  举报