牛客竞赛数学专题班同余与模 题解

【模板】同余方程

模板题,直接用exgcd就行了,上一场的青蛙的约会搞懂了,自然就会了。

#include 
#define int long long
using namespace std;

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

    return res;
}

int T, a, b, x, y, d;
signed main() {
    cin >> T;
    while (T--) {
        cin >> a >> b;
        d = exgcd(a, b, x, y);
        if (d != 1) {
            cout << -1 << "\n";
        } else {
            cout << (x % b + b) % b << "\n";
        }
    }

    return 0;
}

【模板】乘法逆元

方法1:线性求逆元,详细方式推导可以参考oi-wiki https://oi-wiki.org/math/number-theory/inverse/#_5

#include 
#define int long long
using namespace std;

const int N = 3e6 + 10;

int n, p, inv[N];
signed main() {
    cin >> n >> p;
    inv[1] = 1;
    for (int i = 2; i <= n; i++) 
    	inv[i] = (p - (p / i) * inv[p % i] % p) % p;

    for (int i = 1; i <= n; i++) 
    	cout << inv[i] << "\n";

    return 0;
}

方法2:因为 n n n​的数据范围比较大,所以不能用费马小定理+快速幂硬求,考虑如何减少用快速幂求逆元的次数。

对于数组 a 1 , a 2 , a 3 , a 4 a_1,a_2,a_3,a_4 a1,a2,a3,a4​,若要求 a 2 a_2 a2​的逆元,只需要求 a 2 − 1 = ( a 1 a 2 a 3 a 4 ) − 1 ⋅ ( a 1 a 3 a 4 ) a_2^{-1}=(a_1a_2a_3a_4)^{-1}\cdot (a_1a_3a_4) a21=(a1a2a3a4)1(a1a3a4)​,记录一个前缀和一个后缀就行了。(当时写的时候并不会线性求逆元)

#include 
#define int long long
using namespace std;

const int N = 3e6 + 10;
int n, p, pre[N], suf[N];
int k = 1;

int power(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = (__int128)res * a % p;
        a = (__int128)a * a % p;
        b >>= 1;
    }
    return res;
}

int inv(int x) { return power(x, p - 2); }

signed main() {
    cin >> n >> p;
    for (int i = 1; i <= n; i++) k = (__int128)k * i % p;
    k = inv(k);

    pre[0] = suf[n + 1] = 1;
    for (int i = 1; i <= n; i++) pre[i] = (__int128)pre[i - 1] * i % p;
    for (int i = n; i >= 1; i--) suf[i] = (__int128)suf[i + 1] * i % p;

    for (int i = 1; i <= n; i++) {
        int inv_i = (__int128)pre[i - 1] * suf[i + 1] % p * k % p;
        cout << inv_i << "\n;
    }
    return 0;
}

Alternating Sum

牛客竞赛数学专题班同余与模 题解_第1张图片

由上图可知,可以将求和式 ∑ i = 0 n s i a n − i b i \sum_{i=0}^ns_ia^{n-i}b^i i=0nsianibi分成 c n t = ( n + 1 ) / k cnt=(n+1)/k cnt=(n+1)/k个小段,每一段内的求和值为 t [ i ] = t [ i − 1 ] × a − k b k , t [ 1 ] = ∑ i = 0 k − 1 s i a n − i b i t[i]=t[i-1]\times a^{-k}b^k,t[1]=\sum_{i=0}^{k-1}s_ia^{n-i}b^i t[i]=t[i1]×akbk,t[1]=i=0k1sianibi,可以用等比数列 n n n项和的公式求出,公比为 a − k b k a^{-k}b^k akbk

注意:当公比为1时,不能用等比数列前 n n n项和来计算,需要特判。

对于最后剩下的长度不足 k k k​的一段,可以直接暴力求解。

#include 
#define int long long
using namespace std;

const int N = 1e5 + 10;
const int mod = 1e9 + 9;

int n, a, b, k, pre[N];
int res, w;
char s[N];

int power(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int inv(int x) { return power(x, mod - 2); }

signed main() {
    scanf("%lld%lld%lld%lld", &n, &a, &b, &k);
    scanf("%s", s);
    w = power(b, k) * inv(power(a, k)) % mod;
    for (int i = 0; i < k; i++) {
        if (i != 0) pre[i] = pre[i - 1];
        if (s[i] == '+') {
            pre[i] = (pre[i] + power(a, n - i) * power(b, i) % mod) % mod;
        } else {
            pre[i] = (pre[i] - power(a, n - i) * power(b, i) % mod + mod) % mod;
        }
    }
    int cnt = (n + 1) / k;
    if (w == 1) {    // 特判分母为0的情况
        res = pre[k - 1] * cnt % mod;
    } else {    // 等比数列前cnt项和
        res = pre[k - 1] * ((power(w, cnt) - 1 + mod) % mod) % mod *
              inv((w - 1 + mod) % mod) % mod;
    }

    res = (res + pre[n - (n + 1) / k * k] * power(w, (n + 1) / k) % mod) % mod;

    printf("%lld", res);

    return 0;
}

扩展中国剩余定理

直接套excrt的板子。

#include 
#define int long long
using namespace std;

const int N = 1e5 + 10;

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

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

int lcm(int a, int b) { return a / gcd(a, b) * b; }

int excrt(int k, int *a, int *r) {
    int M = r[1], ans = a[1];
    for (int i = 2; i <= k; i++) {
        int x0, y0;
        int c = a[i] - ans;
        int g = exgcd(M, r[i], x0, y0);
        if (c % g != 0) return -1;
        x0 = (__int128)x0 * (c / g) % (r[i] / g);
        ans = x0 * M + ans;
        M = lcm(M, r[i]);
        ans = (ans % M + M) % M;
    }
    return ans;
}

int n, a[N], b[N];
signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i] >> b[i];
    int res = excrt(n, b, a);
    cout << res;

    return 0;
}

Biorhythms

三重峰值出现时,一定满足 x = p + 23 t 1 = e + 28 t 2 = i + 33 t 3 x=p+23t_1=e+28t_2=i+33t_3 x=p+23t1=e+28t2=i+33t3​​,可以列出同余方程为
{ x ≡ p ( m o d    23 ) x ≡ e ( m o d    28 ) x ≡ i ( m o d    33 ) \left\{\begin{matrix} x\equiv p(\mod 23) \\ x\equiv e(\mod 28) \\ x\equiv i(\mod 33) \end{matrix}\right. xp(mod23)xe(mod28)xi(mod33)
直接套excrt,求出通解。

注意题目要求的是从日期d​到下一个三重峰的天数,要求出特解。

#include 
#define int long long
using namespace std;

const int N = 1e5 + 10;

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

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

int lcm(int a, int b) { return a / gcd(a, b) * b; }

pair<int, int> excrt(int k, int *a, int *r) {
    int M = r[1], ans = a[1];
    for (int i = 2; i <= k; i++) {
        int x0, y0;
        int c = a[i] - ans;
        int g = exgcd(M, r[i], x0, y0);
        if (c % g != 0) return {-1, -1};
        x0 = (__int128)x0 * (c / g) % (r[i] / g);
        ans = x0 * M + ans;
        M = lcm(M, r[i]);
        ans = (ans % M + M) % M;
    }
    return {ans, M};
}

int T, p, e, i, d;
int a[10], b[10];
signed main() {
    cin >> T;
    while (T--) {
        cin >> p >> e >> i >> d;
        a[1] = p, b[1] = 23;
        a[2] = e, b[2] = 28;
        a[3] = i, b[3] = 33;
        pair<int, int> w = excrt(3, a, b);
        w.first -= d;
        w.first = (w.first % w.second + w.second) % w.second;
        if (w.first == 0) w.first = w.second;
        cout << w.first << "\n";
    }

    return 0;
}

Notepad

欧拉降幂裸题。

牛客竞赛数学专题班同余与模 题解_第2张图片

所有 n n n b b b进制的数有 b n b^n bn个,含前导 0 0 0的数有 b n − 1 b^{n-1} bn1个,总共需要记录的数有 b n − b n − 1 = ( b − 1 ) b n − 1 b^n-b^{n-1}=(b-1)b^{n-1} bnbn1=(b1)bn1​个,直接对 c c c​取模。

当取模结果为 0 0 0时,最后一页记录的数字是 c c c个,需要特判一下。

#include 
#define int long long
using namespace std;

const int N = 1e5 + 10;

int power(int a, int b, int mod) {
    int res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int f(int n) {	// 求n的欧拉函数
    int res = n;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            res = res / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) res = res / n * (n - 1);
    return res;
}

int b, n, c;
string str_b, str_n;

signed main() {
    cin >> str_b >> str_n >> c;

    for (int i = 0; i < str_b.size(); i++) b = (b * 10 + (str_b[i] - '0')) % c;

    int phi_c = f(c);
    bool k = false;
    for (int i = 0; i < str_n.size(); i++) {
        n = n * 10 + (str_n[i] - '0');
        if (n - 1 >= phi_c) n %= phi_c, k = true;
    }
    n--;
    if (k) n += phi_c;
    int res = (b - 1 + c) % c * power(b, n, c) % c;
    if (!res) res = c;
    cout << res;
    
    return 0;
}

Power Tower

m → φ ( m ) → φ ( φ ( m ) ) → . . . → 1 m\rightarrow \varphi(m) \rightarrow \varphi(\varphi(m)) \rightarrow...\rightarrow1 mφ(m)φ(φ(m))...1 ,这个步骤最多会进行 log ⁡ m \log m logm次。

对于每次的查询区间 [ l , r ] [l,r] [l,r],最多计算 log ⁡ m \log m logm次,总的复杂度就是 O ( q log ⁡ m ) O(q\log m) O(qlogm)

计算幂的时候要用到欧拉降幂

#include 
#define int long long
using namespace std;

const int N = 1e5 + 10;

int n, m, w[N], q;
int phi_m[N], total;
bool k;

int power(int a, int b, int mod) {
    int res = 1;
    while (b) {
        if (b & 1) {
            res = res * a;
            if (res >= mod) {
                k = true;
                res %= mod;
            }
        }
        a = a * a;
        if (a >= mod) {
            k = true;
            a %= mod;
        }
        b >>= 1;
    }
    return res;
}

int f(int n) {
    int res = n;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            res = res / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) res = res / n * (n - 1);
    return res;
}

void init() {
    int tmp = m;
    total = 0;
    phi_m[++total] = tmp;
    while (tmp != 1) {
        tmp = f(tmp);
        phi_m[++total] = tmp;
    }
}

void solve(int l, int r) {
    r = min(r, l - 1 + total);
    int res = 1;
    for (int i = r; i >= l; i--) {
        k = false;
        res = power(w[i], res, phi_m[i - l + 1]);
        if (k) res += phi_m[i - l + 1];
    }
    cout << res % phi_m[1] << "\n";
}

signed main() {
    cin >> n >> m;
    init();  // 预处理欧拉函数的值
    for (int i = 1; i <= n; i++) cin >> w[i];
    cin >> q;
    while (q--) {
        int l, r;
        cin >> l >> r;
        solve(l, r);
    }
    return 0;
}

GCD Table

若选中的起始位置为 ( i , j ) (i,j) (i,j)​,则:
{ g c d ( i , j ) ) = a 1 g c d ( i , j + 1 ) ) = a 2 g c d ( i , j + 2 ) ) = a 3 ⇔ { j ≡ 0 ( m o d    a 1 ) j ≡ − 1 ( m o d    a 2 ) j ≡ − 2 ( m o d    a 3 ) \begin{matrix} \left\{\begin{matrix} gcd(i,j)) &=& a_1 \\ gcd(i,j+1)) &=& a_2 \\ gcd(i,j+2)) &=& a_3 \end{matrix}\right. & \Leftrightarrow & \left\{\begin{matrix} j &\equiv& 0(\mod a_1) \\ j &\equiv& -1 (\mod a_2) \\ j &\equiv& -2 (\mod a_3) \end{matrix}\right. \end{matrix} gcd(i,j))gcd(i,j+1))gcd(i,j+2))===a1a2a3jjj0(moda1)1(moda2)2(moda3)
上式可以通过excrt求得 j j j​​​​​的通解: j = j 0 + M ⋅ x ,   M = l c m ( a 1 , a 2 . . . a k ) j=j_0+M\cdot x,\ M=lcm(a_1,a_2...a_k) j=j0+Mx, M=lcm(a1,a2...ak)​​​,且 i i i​​​​​​​​​是 M M M​​​​​​​​​​​的倍数, i = M + M ⋅ y i=M+M\cdot y i=M+My

g = g c d ( M , j 0 ) g=gcd(M,j_0) g=gcd(M,j0)​, g ′ = g c d ( M + M ⋅ y , j 0 + M ⋅ x ) g'=gcd(M+M\cdot y,j_0+M\cdot x) g=gcd(M+My,j0+Mx)​,可以看出 g ∣ g ′ g|g' gg

只需要判断 j = j 0 , i = M j=j_0,i=M j=j0,i=M​是否成立就行了,因为 g c d ( i , j + t ) gcd(i,j+t) gcd(i,j+t)有可能是 a t + 1 a_{t+1} at+1​​​​的倍数,这样条件就不成立。

#include 
#define int long long
using namespace std;

const int N = 1e4 + 10;

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

int lcm(int a, int b) { return a / __gcd(a, b) * b; }

pair<int, int> excrt(int k, int *a, int *r) {
    int M = r[1], ans = a[1];
    for (int i = 2; i <= k; i++) {
        int x0, y0;
        int c = a[i] - ans;
        int g = exgcd(M, r[i], x0, y0);
        if (c % g != 0) return {-1, -1};
        x0 = (__int128)x0 * (c / g) % (r[i] / g);
        ans = x0 * M + ans;
        M = lcm(M, r[i]);
        ans = (ans % M + M) % M;
    }
    return {ans, M};
}

int n, m, k, a[N];
int b[N];
signed main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= k; i++) cin >> a[i], b[i] = -(i - 1);

    pair<int, int> x = excrt(k, b, a);
    if (x.first == 0) x.first = x.second;

    if (x.first > m - k + 1 || x.second > n || x.first == -1) {
        cout << "NO";
    } else {
        for (int i = 0; i < k; i++)
            if (__gcd(x.first + i, x.second) != a[i + 1]) {
                cout << "NO";
                return 0;
            }
        cout << "YES";
    }
    return 0;
}

你可能感兴趣的:(ACM数学,逆元,扩展欧几里得定理,扩展中国剩余定理,数学)