欧几里得算法

  • gcd,\(O(\log n)\)
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
  • lcm,\(O(\log n)\)
// 注意数据范围,过大的数要用__int128
ll lcm(ll a, ll b) {return a * b / gcd(a, b);}
  • 扩展欧几里得,\(\log n\)
ll exgcd(ll a, ll b, ll &x, ll &y) { // ax + by = gcd(a, b)
    if(b == 0) {
        y = 0, x = 1;
        return a;
    }  
    ll res = exgcd(b, a%b, x, y);
    ll tx = x,ty = y;
    x = ty;
    y = tx - (a / b) * ty;
    return res;
}
bool solve(ll a, ll b, ll c, ll &x, ll &y) { // ax + by = c, gcd(a, b)|c, x>=0
    if(!c) {
        x = y = 0;
        return true;
    }
    ll d = exgcd(a, b, x, y);
    if(c % d) return false;
    a /= d, b /= d, c /= d;
    ll ret = c % b;
    x = x * ret;
    x = x + (b - x) / b * b; // 调整此处控制x下界
    y = (c - a * x) / b;
    return true;
}

欧拉定理&费马小定理

\[a^b\equiv \begin{cases} a^{b \ \bmod \ \varphi(p)},\,&\gcd(a,\,p)=1\\ a^b,&\gcd(a,\,p)\ne1,\,b\lt \varphi(p)\\ a^{b \ \bmod \ \varphi(p)+\varphi(p)},&\gcd(a,\,p)\ne1,\,b\ge\varphi(p) \end{cases} \pmod p \]

注意模数和底数不互质时的处理。

ll getphi(ll n) {
    ll p = n;
    for(int i = 0; 1ll * pri[i] * pri[i] < p; i++) {
        if(n % pri[i] == 0) {
            p = p / pri[i] * (pri[i] - 1);
            while(n % pri[i] == 0) n /= pri[i];
        } 
    }
    if(n > 1) p = p / n * (n - 1);
    return p;
}

乘法逆元

  • 线性求逆元
// i无逆元时,inv[i]是未定义的
inv[1] = 1;
for(int i = 2; i <= n; i++) {
    inv[i] = (ll)(p - p / i) * inv[p % i] % p;
}
  • 扩展欧几里得
ll inv(ll a, ll p) {
    ll x, y;
    exgcd(a, p, x, y);
    return (x % p + p) % p; 
}

中国剩余定理(CRT)

// a, r下标均从1开始
// crt
ll crt(int k, ll a[], ll r[]) { // xi mod ai = ri, ai模数必须互质
    ll n = 1, ans = 0;
    for(int i = 1; i <= k; i++) n = n * a[i];
    for(int i = 1; i <= k; i++) {
        ll m = n / a[i], b, y;
        exgcd(m, a[i], b, y); // b * m mod a[i] = 1
        ans = (ans + r[i] * m * b % n) % n;
    }
    return (ans % n + n) % n;
}
// 任意模数crt
ll excrt(int n, ll a[], ll r[]) { //xi mod ai = ri, 无解时返回-1
    ll res, m, x, y;
    res = r[1];
    m = a[1];
    for(int i = 2; i <= n; i++) {
        ll d = ((r[i] - res) % a[i] + a[i]) % a[i];
        ll g = exgcd(m, a[i], x, y);
        if(d % g) return -1;
        ll k = d / g;
        ll rm = lcm(a[i], m);
        res = ((__int128)m * x % rm * k + res) % rm;
        m = rm;
    }
    return (res % m + m) % m;
}

二次剩余

  • cipolla,要求模数必须为奇素数,期望时间复杂度\(O(\log n)\)
namespace cipolla {
    ll w;
    struct num {  //建立一个复数域
      ll x, y;
    };
    num mul(num a, num b, ll p) {  //复数乘法
      num ans = {0, 0};
      ans.x = ((a.x * b.x % p + a.y * b.y % p * w % p) % p + p) % p;
      ans.y = ((a.x * b.y % p + a.y * b.x % p) % p + p) % p;
      return ans;
    }
    ll qpow_img(num a, ll b, ll p) {  //虚部快速幂
      num ans = {1, 0};
      while (b) {
        if (b & 1) ans = mul(ans, a, p);
        a = mul(a, a, p);
        b >>= 1;
      }
      return ans.x % p;
    }
    ll solve(ll n, ll p) { // x^2 mod p = n
          n %= p;
          if (p == 2) return n;
          if (qpow(n, (p - 1) / 2, p) == p - 1) return -1;
          ll a;
          while (1) {  //生成随机数再检验找到满足非二次剩余的a
                a = rand() % p;
                w = ((a * a % p - n) % p + p) % p;
                if (qpow(w, (p - 1) / 2, p) == p - 1) break;
          }
          num x = {a, 1};
          return qpow_img(x, (p + 1) / 2, p);
    }
}
  • BSGS,求任意次开根,即求\(x^a\equiv b \mod p\)\(p\)为奇素数。时间复杂度\(O(\sqrt{n})\)
ll getroot(ll p) { // p是素数,求p的原根
    ll phi = p - 1, tp = phi;
    vector<ll> factors;
    for(int i = 2; 1ll * i * i <= phi; i++) {
        if(tp == 1) break;
        if(tp % i == 0) {
            while(tp % i == 0) tp /= i;
            factors.push_back(i);
        }
    }
    ll g = 2;
    while(g < p) {
        bool ok = true;
        for(ll d : factors) {
            if(qpow(g, phi / d, p) == 1) {
                ok = false;
                break;
            }
        }
        if(ok) return g;
        g++;
    } 
    return -1;
}

vector<ll> ans;
void solve(ll a, ll b, ll p) { // 获得x^a % p = b所有解,保存在数组ans中
    if(b == 0) {
        ans.push_back(0);
        return ;
    }
    ll g = getroot(p), phi = p - 1;
    ll d = phi / gcd(a, phi);
    ll c = bsgs(qpow(g, a, p), b, p);
    if(c == -1) return ;
    for(ll i = c % d; i < phi; i += d) {
        ans.push_back(qpow(g, i, p));
    }
    sort(ans.begin(), ans.end());
}

离散对数

  • exbsgs,求解\(a^x\equiv b\mod p\),其中\(a\)不必和\(p\)互质,时间复杂度\(O(\sqrt{n})\)
ll inv(ll a, ll p) {
    ll x, y;
    exgcd(a, p, x, y);
    return (x % p + p) % p; 
}

map<ll, int> ex;
ll bsgs(ll a, ll b, ll p) { // a^x % p = b; a, p必须互质
    ex.clear();
    int sqp = ceil(sqrt(p));
    ll pab = b % p, pw = qpow(a, sqp, p), pa = pw;
    for(int i = 0; i <= sqp; i++) {
        ex[pab] = i;
        pab = pab * a % p;
    }
    for(int i = 1; i <= sqp; i++) {
        if(ex.count(pa)) {
            ll res = 1ll * i * sqp - ex[pa];
            return res;
        }
        pa = pa * pw % p;
    }
    return -1;
}

ll exbsgs(ll a, ll b, ll p) {
    b %= p;
    a %= p;
    ll d = 1, tp = p, tb = b, pwa = 1;
    int k = 0;
    while(tb != 1 && (d = gcd(a, tp)) != 1) {
        if(b % d != 0) return -1;
        if(tb % d != 0) break;
        tp /= d;
        tb /= d;
        k++; 
    }
    for(int i = 0; i <= k; i++) {
        if(pwa == b) return i;
        pwa = pwa * a % p;
    }
    if(gcd(a, tp) != 1) return -1;
    ll res = bsgs(a % tp, b * inv(qpow(a, k, tp), tp) % tp, tp);
    if(res == -1) return -1;
    return res + k;
}

卢卡斯定理

  • 递推式求组合数
for(int i = 1; i <= n; i++) C[i][0] = C[i][i] = 1;
for(int i = 1; i <= n; i++) {
    for(int j = 1; j < i; j++) {
        C[i][j] = (C[i-1][j-1] + C[i-1][j]) % M; // M = mod
    }
}
  • 公式法求组合数,注意若模数为\(p\)。当\(n< p\)可以用公式法,\(n\ge p\)时请使用其他方法,否则可能会出现模数为0导致结果出错。

\[C(n,m)=\frac{n!}{(n-m)!m!} \]

  • 卢卡斯定理:求\(C(n, m)\mod p\)​,\(p\)​为素数,时间复杂度\(O(f(p) + g(p)\log n)\)​。其中\(f(p)\)​代表预处理值域为前\(p\)​组合数的复杂度,\(g(p)\)​为计算单个组合数的复杂度。若直接暴力计算,时间复杂度\(O(p\log p)\)​,
ll lucas(ll n, ll m, ll p) {
    if(m == 0) return 1;
    return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
}
  • 扩展卢卡斯定理:求\(C(n, m)\mod p\)\(p\)为任意模数,时间复杂度\(O(p\log p)\)。预处理+p固定+若干询问:\(O(p+T\log p)\)\(T\)为询问次数。
ll calc(ll n, ll x, ll P) {
    if(!n) return 1;
    ll s = 1;
    for(ll i = 1; i <= P; i++) // 此处可以预处理
        if(i % x) s = s * i % P;
    s = qpow(s, n / P, P);
    for(ll i = 1; i <= n % P; i++) // 此处可以预处理
        if(i % x) s = i * s % P;
    return s * calc(n / x, x, P) % P;
}

ll multilucas(ll n, ll m, ll x, ll P) {
    int cnt = 0;
    for(ll i = n; i; i /= x) cnt += i / x;
    for(ll i = m; i; i /= x) cnt -= i / x;
    for(ll i = n - m; i; i /= x) cnt -= i / x;
    // inv使用exgcd的
    return qpow(x, cnt, P) % P * calc(n, x, P) % P * inv(calc(m, x, P), P) % P * inv(calc(n - m, x, P), P) % P;
}

ll exlucas(ll n, ll m, ll P) { // n >= m, C(n, m)
    if(m > n) return 0; 
    int cnt = 0;
    ll p[20], a[20];
    for(ll i = 2; i * i <= P; i++) {
        if(P % i == 0) {
            p[++cnt] = 1;
            while (P % i == 0) p[cnt] = p[cnt] * i, P /= i;
            a[cnt] = multilucas(n, m, i, p[cnt]);
        }
    }
    if(P > 1) p[++cnt] = P, a[cnt] = multilucas(n, m, P, P);
    return excrt(cnt, p, a);
}