Loading

解题报告 数论 2024/12/28 ~ 2025/01/22

Link

分班了,现在来还欠下的债。


算术基本定理

内容

任何一个大于 \(1\) 的正整数都能唯一分解成有限个质数的乘积,可写作:

\[N=p_1^{c_1}p_2^{c_2}\dots p_m^{c_m} \]

其中 \(c_i\) 都是正整数,\(p_i\) 都是质数,且满足 \(p_1\lt p_2\lt\dots\lt p_m\)

推论

  • \(N\) 的正约数集合可写作 \(\{p_1^{b_1}p_2^{b_2}\dots p_m^{b_m}\}\),其中 \(0\le b_i \le c_i\)

  • \(N\) 的正约数个数为 \(\prod_{i=1}^m(c_i+1)\)

  • \(N\) 的所有正约数的和为 \(\prod_{i=1}^m(\sum_{j=0}^{c_i}(p_i)^j)\)

例题

A. \(T\) 组数据,每次给定一个数 \(n\),求在 \([1,n]\)约数个数最多的数 的 约数个数\(1\le t\le 500, 1\le n\le 10^{18}\)

根据算术基本定理,将 \(n\) 分解成 \(p_1^{c_1}p_2^{c_2}\dots p_m^{c_m}\) 的形式,并且由第二条推论,容易发现约数个数只和 \(c_i\) 有关。于是我们显然希望对于一个较小的 \(p_i\),有较大的指数 \(c_i\),这样得到的答案肯定更优,也就是说 \(c_i\) 是(非严格)单调递减的。

于是我们考虑处理出所有可能出现的 \(p_i\),并且发现 \(p_i\) 的个数很少,所以可以 DFS 依次确定对应的每一个 \(c_i\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int base[20] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int T, n, ans;

int qpow(int a, int b)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

void DFS(int pos, int cnt, int cur, int rng)
{
    if (cur > n) return;
    ans = max(ans, cnt);
    for (int i = 1; i <= rng; i++)
    {
        int tmp = qpow(base[pos], i);
        if (cur > n / tmp) break;
        DFS(pos + 1, cnt * (i + 1), cur * tmp, i);
    }
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        cin >> n;
        ans = 0;
        DFS(1, 1, 1, 63);
        cout << ans << endl;
    }
    return 0;
}

B. \(T\) 组数据,每次给定两个数 \(p\)\(q\),求最大的整数 \(x\) 使得 \(x\)\(p\) 的因数且 \(q\) 不是 \(x\) 的因数。\(1\le T\le 50,1\le p\le 10^{18},2\le q\le 10^9\)

\(q\) 不是 \(p\) 的因数时,答案显然为 \(p\)

否则将 \(p\)\(q\)\(x\) 都表示成算术基本定理的形式,为了避免混乱,不妨将其指数分别表示成 \(a_i\)\(b_i\)\(c_i\) 容易发现一个 \(x\) 可能最优当且仅当存在一个 \(c_i=b_i-1\),其他的 \(c_i=a_i\)

但是对 \(p\) 分解质因数的复杂度可能是难以接受的,我们可以只将 \(q\) 分解,对于每一个因数 \(p_i\),对 \(p\) 进行试除,不难发现指数最大不超过 \(63\),时间复杂度 \(O(能过)\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 10000 + 10;

int T, p, q, x;
int m, pri[maxn], c[maxn];

int qpow(int a, int b)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

void init(int n)
{
    m = 0;
    for (int i = 2; i <= sqrt(n); i++)
        if (n % i == 0)
        {
            pri[++m] = i, c[m] = 0;
            while (n % i == 0) n /= i, c[m]++;
        }
    if (n > 1)
        pri[++m] = n, c[m] = 1;
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        cin >> p >> q;
        if (p % q != 0)
        {
            cout << p << endl;
            continue;
        }
        else
        {
            x = 1, init(q);
            for (int i = 1; i <= m; i++)
            {
                int tmp = p, cnt = 0;
                while (tmp % pri[i] == 0) tmp /= pri[i], cnt++;
                x = max(x, p / qpow(pri[i], cnt - c[i] + 1));
            }
            cout << x << endl;
        }
    }
    return 0;
}

C. 给定整数 \(n\),试把阶乘 \(n!\) 分解质因数,按照算术基本定理的形式输出结果中的 \(p_i\)\(c_i\)\(1\le n\le 10^6\)

显然不能强行分解。

考虑 \(n!\) 中质因子 \(p\) 的个数实际上就是 \(1\sim N\) 中每个数包含质因子 \(p\) 的个数之和。

  • 至少包含 \(1\) 个质因子 \(p\) 的数,即 \(p\) 的倍数,显然有 \(\lfloor \dfrac{N}{p}\rfloor\) 个。

  • 至少包含 \(2\) 个质因子 \(p\) 的数,即 \(p^2\) 的倍数,显然有 \(\lfloor \dfrac{N}{p^2}\rfloor\) 个。

  • ......

于是答案为 \(\sum_{p^k\le N} \lfloor\dfrac{N}{p^k}\rfloor\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 1000000 + 10;

int n, m, pri[maxn], c[maxn];
bool v[maxn];

void init()
{
    for (int i = 2; i <= n; i++)
    {
        if (v[i])
            continue;
        pri[++m] = i;
        for (int j = i; j <= n / i; j++)
            v[i * j] = 1;
    }
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    init();
    for (int i = 1; i <= m; i++)
    {
        if (pri[i] > n)
            break;
        for (int j = pri[i]; j <= n; j *= pri[i])
            c[i] += n / j;
        if (c[i])
            cout << pri[i] << ' ' << c[i] << endl;
    }
    return 0;
}

D. 对于任何正整数 \(x\),其约数的个数记作 \(\mathrm g(x)\)。求不超过 \(n\) 的满足 \(\forall i \in(0,x),\mathrm g(x)>\mathrm g(i)\) 的最大的 \(x\)\(1\le n\le 2\times 10^9\)

\(x\) 即是 \(1\sim n\) 中约数个数最多的数中最小的一个,正确性显然。

于是转化为 A 的双倍经验。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int base[20] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int n, ans, ans_cnt;

int qpow(int a, int b)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

void DFS(int pos, int cnt, int cur, int rng)
{
    if (cur > n)
        return;
    if (cnt > ans_cnt)
    {
        ans_cnt = cnt;
        ans = cur;
    }
    if (cnt == ans_cnt)
        ans = min(ans, cur);
    for (int i = 1; i <= rng; i++)
    {
        int tmp = qpow(base[pos], i);
        if (cur > n / tmp)
            break;
        DFS(pos + 1, cnt * (i + 1), cur * tmp, i);
    }
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    ans_cnt = 0;
    DFS(1, 1, 1, 63);
    cout << ans << endl;
    return 0;
}

E. \(T\) 组数据,给定正整数 \(N\),已知 \(N = p^2\times q\) 且该分解形式唯一。求 \(p\)\(q\)\(1\le T\le 10, 1\le N\le 9\times 10^{18}\)

注意到 \(p,q \le \sqrt[3]{N}\approx 2,080,084\),并且 \(p,q\) 唯一。

不妨直接筛出这个范围内的素数,然后枚举判断。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 2100000 + 10;

int T, n, m, pri[maxn];
bool v[maxn];

void init()
{
    for (int i = 2; i <= 2100000; i++)
    {
        if (v[i])
            continue;
        pri[++m] = i;
        for (int j = i; j <= 2100000 / i; j++)
            v[i * j] = 1;
    }
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    init();
    while (T--)
    {
        cin >> n;
        for (int i = 1; i <= m; i++)
            if (n % pri[i] == 0)
            {
                int x = pri[i];
                if (n % (x * x) == 0)
                    cout << x << ' ' << n / (x * x) << endl;
                else
                    cout << (int)sqrt(n / x) << ' ' << x << endl;
                break;
            }
    }
    return 0;
}

F. 求不定方程 \(\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{1}{n!}\) 正整数解 \((x,y)\) 的数目,答案对 \(10^9+7\) 取模。\(1\le n\le 10^6\)

推式子。

\[\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{1}{n!} \]

\[\dfrac{x+y}{xy}=\dfrac{1}{n!} \]

\[n!(x+y)=xy \]

然后就不会了,但是注意到 \(x,y\ge n!\),不妨设 \(x=n!+a, y=n!+b\) 并继续化简。

\[n!(2n!+a+b)=(n!+a)(n!+b) \]

\[2(n!)^2+n!(a+b)=(n!)^2+n!(a+b)+ab \]

\[(n!)^2=ab \]

因为每一组 \((a,b)\) 都唯一对应一组 \((x,y)\)\((a,b)\) 的组数即为答案。而 \((a,b)\) 的组数显然等于 \((n!)^2\) 的因数个数。

于是问题转化为 C 的双倍经验,最后应用算术基本定理的推论求出 \((n!)^2\) 的约数个数即可。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 1'000'000 + 10;
const int mod = 1'000'000'000 + 7;

int n, m, pri[maxn], c[maxn];
bool v[maxn];

void init()
{
    for (int i = 2; i <= n; i++)
    {
        if (v[i])
            continue;
        pri[++m] = i;
        for (int j = i; j <= n / i; j++)
            v[i * j] = 1;
    }
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    init();
    for (int i = 1; i <= m; i++)
    {
        if (pri[i] > n)
            break;
        for (int j = pri[i]; j <= n; j *= pri[i])
            c[i] = (c[i] % mod + n / j % mod) % mod;
        c[i] = c[i] * 2 % mod;
    }
    int ans = 1;
    for (int i = 1; i <= m; i++)
        ans = (ans % mod * (c[i] + 1) % mod) % mod;
    cout << (ans + mod) % mod;
    return 0;
}

G. \(T\) 组数据,求 \(\sum_{i=1}^n\sum_{j=i}^n [\mathrm{lcm}(i,j)=n]\),其中 \([]\) 为艾佛森括号。

\(n\) 分解质因数,有 \(n=p_1^{c_1}p_2^{c_2}\dots p_m^{c_m}\)

\(i,j\) 都是 \(n\) 的因数,那么有 \(i=p_1^{x_1}p_2^{x_2}\dots p_m^{x_m},j=p_1^{y_1}p_2^{y_2}\dots p_m^{y_m}\),若 \(i,j\) 中不含某个质因子 \(p_i\),则令 \(x_i,y_i=0\) 以方便形式上统一。

注意到实际上 \(\mathrm{lcm}(i,j)=p_1^{\max(x_1,y_1)}p_2^{\max(x_2,y_2)}\dots p_m^{\max(x_m,y_m)}\)

那么对于一个 \((x_i,y_i)\),需要保证 \(\max(x_i,y_i)=c_i\),也就是说 \(x_i,y_i\) 之中必须有一个是 \(c_i\),另一个可以取 \([0,c_i]\)。于是一共有 \(2c_i+1\) 种方案。

那么最终的结果即为 \(\dfrac{\prod^{n}_{i=1}(2c_i+1)}{2}+1\),即排除了 \(i>j\) 的情况,并且算上了 \(i=j=n\) 的情况。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 10000 + 10;

int T, cas, n;
int m, pri[maxn], c[maxn];

void init(int n)
{
    m = 0;
    for (int i = 2; i <= sqrt(n); i++)
        if (n % i == 0)
        {
            pri[++m] = i, c[m] = 0;
            while (n % i == 0)
                n /= i, c[m]++;
        }
    if (n > 1)
        pri[++m] = n, c[m] = 1;
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T;
    while (T--)
    {
        int ans = 1;
        cin >> n;
        init(n);
        for (int i = 1; i <= m; i++)
            ans *= (2 * c[i] + 1);
        ans = ans / 2 + 1;
        cout << "Case " << ++cas << ": " << ans << endl;
    }
    return 0;
}

H.\(A^B\) 的所有自然因子之和 \(\bmod\ 9901\) 的值。\(0\le A,b\le 5\times 10^7\)

首先回顾一下推论:\(N\) 的所有正约数的和为 \(\prod_{i=1}^m(\sum_{j=0}^{c_i}(p_i)^j)\)

把式子展开:\((1+p_1+p_1^2+\dots + p_1^{c_1})\times\dots\times(1+p_m+p_m^2+\dots+p_m^{c_m})\)

对于这里,显然就是 \(S=(1+p_1+p_1^2+\dots + p_1^{B\times c_1})\times\dots\times(1+p_m+p_m^2+\dots+p_m^{B\times c_m})\)

不难发现每一个括号里都是一个等比数列。

代入等比数列的求和公式 \(S_n=\dfrac{a_1(1-q^n)}{1-q}\)(其中 \(q\) 为公比且 \(q\not = 1\)),不难得到这里每一项的公式 \(\dfrac{p_1^{B\times c_i+1}-1}{p_1-1}\)

那么思路就是很显然的,用快速幂求出 \((p_1^{B\times c_i+1}-1) \bmod{9901}\)\((p_1-1)\bmod{9901}\)。因为 \(9901\) 是质数,只要 \(p_1-1\) 不是 \(9901\) 的倍数,那么求逆元 \(inv\) 即可;否则因为 \(p_1\bmod{9901} =1\),显然有 \(1+p_1+p_1^2+\dots + p_1^{B\times c_1}\equiv 1+1+1^2+\dots+1^{B\times c_i+1}\equiv B\times c_i+1\pmod{9901}\)

但是我做到这个题的时候还不会求逆元啊喂。

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn = 50 + 10;
const int mod = 9901;

int a, b, ans = 1;
int m, pri[maxn], c[maxn];

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

void init(int n)
{
    m = 0;
    for (int i = 2; i <= sqrt(n); i++)
        if (n % i == 0)
        {
            pri[++m] = i, c[m] = 0;
            while (n % i == 0)
                n /= i, c[m]++;
        }
    if (n > 1)
        pri[++m] = n, c[m] = 1;
    return;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> a >> b;
    if (a == 0)
    {
        cout << 0 << endl;
        return 0;
    }
    init(a);
    for (int i = 1; i <= m; i++)
    {
        if (pri[i] - 1 % mod == 0)
            ans = ans * ((b * c[i] + 1) % mod) % mod;
        else
        {
            int x = (qpow(pri[i], b * c[i] + 1) - 1 + mod) % mod;
            int y = qpow(pri[i] - 1, mod - 2);
            ans = ans * x % mod * y % mod;
        }
    }
    cout << ans << endl;
    return 0;
}

扩展欧几里得算法

扩展欧几里得算法(EXGCD)常用于求 \(ax+by=\gcd(a,b)\) 的一组可行解。

不定方程

这里只讨论形如 \(ax+by=c\) 的不定方程。

裴蜀定理

对于整数 \(a\)\(b\),一定存在一组整数 \((x,y)\) 使得 \(ax+by=\gcd(a,b)\)

推论

对于整数 \(a\)\(b\)\(c\)\(ax+by=c\) 有解当且仅当 \(\gcd(a,b)\mid c\)。这很显然是对的。

扩展欧几里得算法

考虑欧几里得算法求最大公约数的做法,\(\gcd(a,b)=\gcd(b,a\bmod b)\),我们不难想到在这里求解 \((x,y)\) 时也需要一种特定的方法对 \((x,y)\) 进行迭代从而求解。

考虑迭代过程中相邻的两步。

因为 \(ax_1+by_1=\gcd(a,b)\)\(bx_2+(a\bmod b)y_2=\gcd(b,a\bmod b)\),由欧几里得算法,有 \(\gcd(a,b)=\gcd(b,a\bmod b)\)

所以 \(ax_1+by_1=bx_2+(a\bmod b)y_2\)

又因为 \(a\bmod b=a-(\lfloor\dfrac{a}{b}\rfloor\times b)\)

所以 \(ax_1+by_1=bx_2+(a-(\lfloor\dfrac{a}{b}\rfloor\times b))y_2\)

整理上式,得到 \(ax_1+by_1=ay_2+b(x_2-\lfloor\dfrac{a}{b}\rfloor y_2)\)

因为 \(a=a\)\(b=b\)

所以 \(x_1=y_2\)\(y_1=x_2-\lfloor\dfrac{a}{b}\rfloor y_2\)

结束。

int a, b, x, y;

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

I. 求关于 \(x\) 的同余方程 \(ax\equiv 1\pmod b\) 的最小正整数解,保证有解。\(2\le a,b\le 2\times 10^9\)

考虑对式子进行变形。

\(ax\equiv 1\pmod b\)

\(ax = kb + 1\),我们不妨令 \(k=-y\),移项。

\(ax+by=1\)

于是变成了扩展欧几里得算法的标准式。此外不难发现本题中 \(a\)\(b\) 必定互质。

接下来考虑答案处理,我们用扩展欧几里得算法求出的 \(x\) 一定能使方程成立,但有可能是负数,也不一定是最小的正整数。所以我们需要遍历所有的 \(x+kb\) 找到符合要求的解,具体实现上,只需要 x = (x % b + b) % b 即可。

#include <bits/stdc++.h>
using namespace std;

int a, b, x, y;

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

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> a >> b;
    exgcd(a, b, x, y);
    cout << (x % b + b) % b << endl;
    return 0;
}

J. 青蛙的约会 简要题意:解关于 \(k\) 的方程 \(x+km\equiv y+kn \pmod l\)

考虑对式子进行变形。

\(x+km-(y+kn)=zl\),其中 \(z\in \Z\)

但是这个式子的变量名十分丑陋。我们改一下变量名:

\(m_0+xm-(n_0+xn)=-yl\)

\((m-n)x+(m_0-n_0)=-yl\)

\((m-n)x+ly=n_0-m_0\)

于是可以使用扩展欧几里得算法求解。

这里仍然存在几个问题需要注意:

  • 需要处理 \(m-n\lt 0\) 的情况,需要将 \(m-n\)\(n_0-m_0\) 同时加负号。

  • 解得的 \(x\) 要乘上 \(\dfrac{n_0-m_0}{\gcd(m-n,l)}\)

  • 需要求最小解。

#include <bits/stdc++.h>
using namespace std;

#define int long long

int x, y, m0, n0, m, n, l;

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> m0 >> n0 >> m >> n >> l;
    int a = m - n, b = l, c = n0 - m0;
    if (a < 0)
        a = -a, c = -c;
    int gcd = exgcd(a, b, x, y);
    if (c % gcd)
        cout << "Impossible" << endl;
    else
    {
        c /= gcd;
        x *= c;
        int mod = b / gcd;
        cout << (x % mod + mod) % mod << endl;
    }
    return 0;
}

K. 倒酒

设最后的酒量为 \(s\),不难发现有 \(s=ax+by\),实际上其中的 \(x\) 是负数。

要使 \(s=ax+by\) 能成立,显然 \(\gcd(a,b)|s\)

又因为要求最小解,显然 \(s=\gcd(a,b)\)

第一问就做完了。

第二问相当于求 \(x\) 的最大负整数解,也是好做的。求出 \(x\) 以后 \(y\) 自然是确定的。

#include <bits/stdc++.h>
using namespace std;

#define int long long

int a, b, x, y;

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> a >> b;
    int gcd = exgcd(a, b, x, y);
    cout << gcd << endl;
    int mod = b / gcd;
    x = (x % mod + mod) % mod;
    if (x != 0)
        x -= mod;
    y = (gcd - a * x) / b;
    cout << -x << " " << y << endl;
    return 0;
}

L. [NOI2002] 荒岛野人

注意到 \(M\le 10^6\),于是可以从小到大枚举 \(M\) 并依次判断可行性。最小的 \(M\) 显然就是人数 \(n\)

注意到 \(n\le 15\),于是对于每一个 \(M\) 可以枚举每一对可能的 \((i,j)\) 判断其是否会相遇,即当前的 \(M\) 是否合法。

对于每一对 \((i,j)\) 只需要判断 \((C_i+P_ix)-(C_j+P_jx)=-My\) 的最小解 \(x_0\) 是否 \(\gt \min(L_i,L_j)\),变形易得 \((P_i-P_j)x+My=(C_j-C_i)\),可以直接套扩欧。

于是该题转化为 J. 的双倍经验。

另外需要注意无解的情况在此题中代表这一对 \((i,j)\) 是符合条件的,因为 \(i\)\(j\) 到死都不会碰上。

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn = 15 + 10;

int n, m, C[maxn], P[maxn], L[maxn];

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    int maxc = -1;
    for (int i = 1; i <= n; i++)
    {
        cin >> C[i] >> P[i] >> L[i];
        maxc = max(maxc, C[i]);
    }
    for (int ans = maxc; ans <= 1000000; ans++)
    {
        bool flag = 0;
        for (int i = 1; i < n; i++)
        {
            for (int j = i + 1; j <= n; j++)
            {
                int a = P[i] - P[j], b = ans, c = C[j] - C[i], x, y;
                a = (a % ans + ans) % ans;
                int gcd = exgcd(a, b, x, y);
                if (c % gcd)
                    continue;
                else
                {

                    c /= gcd;
                    x *= c;
                    int mod = b / gcd;
                    x = (x % mod + mod) % mod;
                    if (x <= min(L[i], L[j]))
                    {
                        flag = 1;
                        break;
                    }
                }
            }
            if (flag)
                break;
        }
        if (flag)
            continue;
        else
        {
            cout << ans << endl;
            break;
        }
    }
    return 0;
}

乘法逆元

定义

如果 \(ax\equiv 1\pmod p\),则称 \(x\)\(a\) 在模 \(p\) 意义下的乘法逆元,记作 \(a^{-1}\pmod p\)

计算

  • 扩展欧几里得法

    即求解不定方程 \(ax+py=1\)。并且,由裴蜀定理易知存在 \(a\)\(p\) 意义下的逆元当且仅当 \(\gcd(a,p)=1\)\(a\)\(p\) 互质。

  • 快速幂法(费马小定理)

    由费马小定理有:若 \(p\) 为质数,\(\gcd(a,p)=1\),则 \(a^{p-1}\equiv 1\pmod p\)

    所以 \(ax\equiv a^{p-1}\pmod p\),即 \(x\equiv a^{p-2}\pmod p\)

    于是可以使用快速幂求解。

  • 递推法

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

    \(p\) 不为质数时,至少有一个 \(i\) 的逆元不存在,此时该方法正确性无法保证。

M. 给定 \(n\)\(p\),求 \(1\sim n\) 中所有整数在模 \(p\) 意义下的乘法逆元。\(1\le n\le 3\times 10^6, n\lt p\lt 20000528\)\(p\) 为质数。

线性求逆元板子。

#include <bits/stdc++.h>
using namespace std;

#define endl "\n"
#define int long long
const int maxn = 3000000 + 10;

int n, p;
int inv[maxn];

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> p;
    inv[1] = 1;
    for (int i = 2; i <= n; i++)
        inv[i] = (p - p / i) * inv[p % i] % p;
    for (int i = 1; i <= n; i++)
        cout << inv[i] << endl;
    return 0;
}

N. [AHOI2005] 洗牌

观察到每次洗牌后第 \(i\) 张牌会转移到 \(2i\pmod{n+1}\) 的位置。

不难发现题目等价于求解 \(i\times 2^m\equiv l\pmod{n+1}\)

变形以后得到 \(i\times 2^m+k(n+1)=l\),显然 \(\gcd(2^m,n+1)=1\)。那么直接求 \(x\times 2^m+y(n+1)=1\) 的最小正整数解 \(x_0\)\(x_0\times l\) 即为答案。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long
int n, m, l, mod;
int x, y;

int qmul(int a, int b)
{
    int res = 0;
    while (b > 0)
    {
        if (b & 1)
            res = (res + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    return res;
}

int qpow(int a, int b)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = qmul(res, a) % mod;
        a = qmul(a, a) % mod;
        b >>= 1;
    }
    return res;
}

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> m >> l;
    mod = n + 1;
    int t = qpow(2, m);
    int gcd = exgcd(t, mod, x, y);
    x = (x % mod + mod) % mod;
    x = (qmul(x, l) % mod + mod) % mod;
    cout << x << endl;
    return 0;
}

O. \(T\) 组数据,求 \(1\sim n!\) 中与 \(m!\) 互质的数的个数,答案对 \(R\) 取模。\(1\le M\le N\le 10^7,1\le T\le 10^4,2\le R\le 10^9+10\)\(R\) 为质数。

对于所有与 \(m!\) 互质的数,我们不妨分为小于 \(m!\) 和大于 \(m!\) 两部分来考虑。

显然在小于 \(m!\) 的数中,与 \(m!\) 互质的数有 \(\varphi(m!)\) 个。现在考虑如何求出大于 \(m!\) 的部分。

考虑 \(a\)\(b\) 互质即 \(\gcd(a,b)=1\) 时,根据欧几里得算法,有 \(\gcd(b,a\bmod b)=\gcd(a,b)=1\)

\(a\bmod b=a-k\times b\),即 \(\gcd(b,a-k\times b)=1\),所以 \(b\)\(a-k\times b\) 互质。

那么对于一个小于 \(m!\) 且与 \(m!\) 互质的数 \(x\)\(x+k\times m!\) 都与 \(m!\) 互质。

因为 \(m\le n\)\(n!\) 显然是 \(m!\) 的倍数,于是答案即为 \(\dfrac{n!}{m!}\times\varphi(m!)\)

\[\dfrac{n!}{m!}\times\varphi(m!)=m!\times\prod^k_{i=1}\left(\dfrac{p_i-1}{p_i}\right)\times \dfrac{n!}{m!}=n!\times\prod^k_{i=1}\left(\dfrac{p_i-1}{p_i}\right)=n!\times \dfrac{\prod_{i=1}^{k}(p-1)}{\prod_{i=1}^{k}p} \]

显然在 \(m! =p_1^{c_1}p_2^{c_2}\dots p_m^{c_m}\)\(p_i\) 取遍 \(1\sim m!\) 的所有质数,于是计算欧拉函数时可以简化,直接筛出范围内的所有质数然后递推。

兔队提出这样的做法在 \(n\ge R\) 的情况下会被 hack 掉直接输出 0,于是我们需要考虑如何消去 \(R\)

对于 \(n\ge R\),显然要在 \(n!\) 消掉一个 \(R\),对于 \(m\ge R\),需要再在 \(\prod p\) 中消去一个 \(R\)

具体做法参考兔队的。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

const int maxn = 10'000'000;

int T, mod;
int n, m;
int pri_n, pri[maxn + 10], pos[maxn + 10];
bool v[maxn + 10];
int inv[maxn + 10], fac[maxn + 10];
int pri_fac[maxn + 10], pri_inv[maxn + 10];

void init()
{
    for (int i = 2; i <= maxn; i++)
    {
        if (v[i])
            continue;
        pri[++pri_n] = i;
        for (int j = i; j <= maxn / i; j++)
            v[i * j] = 1;
    }
    fac[1] = 1, inv[1] = 1;
    for (int i = 2; i <= maxn; i++)
    {
        if (!v[i])
            pos[i] = pos[i - 1] + 1;
        else
            pos[i] = pos[i - 1];
        if (i != mod)
            fac[i] = 1ll * i * fac[i - 1] % mod;
        else
            fac[i] = fac[i - 1];
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
    }
    pri_fac[0] = 1, pri_inv[0] = 1;
    for (int i = 1; i <= pri_n; i++)
    {
        pri_fac[i] = 1ll * (pri[i] - 1) * pri_fac[i - 1] % mod;
        if (pri[i] != mod)
            pri_inv[i] = 1ll * inv[pri[i] % mod] * pri_inv[i - 1] % mod;
        else
            pri_inv[i] = pri_inv[i - 1];
    }
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> T >> mod;
    init();
    while (T--)
    {
        cin >> n >> m;
        if (n >= mod && m < mod)
            cout << 0 << endl;
        else
            cout << 1ll * fac[n] * pri_fac[pos[m]] % mod * pri_inv[pos[m]] % mod << endl;
    }
    return 0;
}

P. 给出一个有理数 \(c=\dfrac{a}{b}\),求 \(c\bmod 19260817\) 的值。\(0\le a\le 10^{10001},1\le b\le 10^{10001}\)\(a\)\(b\) 不同时是 \(19260817\) 的倍数。

由于 \(a\)\(b\) 很大,不难想到使用快读边读入边取模,然后计算 \(a\times b^{-1}\pmod{19260817}\) 的值。

但是,这为什么是对的?

显然题目等价于求一个最小的正整数 \(x\) 使得 \(x\equiv \dfrac{a}{b}\pmod p\),

同余式两边同乘 \(b\),得到 \(bx\equiv a\pmod p\),在这个式子上将 \(a,b\) 同时模 \(p\),得 \((b\bmod p)x\equiv a \bmod p\pmod p\),那么这就很显然是对的了。

由此,原做法正确性就显然了。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long
const int mod = 19260817;

int a, b;

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x * 10 % mod + ch - '0') % mod;
        ch = getchar();
    }
    return x * f;
}

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    a = read(), b = read();
    if (b == 0)
        cout << "Angry!" << endl;
    else
    {
        int inv = qpow(b, mod - 2);
        cout << a * inv % mod << endl;
    }
    return 0;
}

欧拉函数,欧拉定理和扩展欧拉定理

欧拉函数

欧拉函数,记作 \(\varphi(n)\),表示小于等于 \(n\) 且与 \(n\) 互质的数的个数。

\(\varphi(1)=1\)\(n\) 是质数时,\(\varphi(n)=n-1\)

计算式:\(\varphi(n)=n\times\prod_{质数p\mid n}(1-\dfrac{1}{p})\)

易得只需要在分解质因数的过程中进行计算即可求出 \(n\) 的欧拉函数。

欧拉定理

\(\gcd(a,m)=1\),则 \(a^{\varphi(m)}\equiv 1\pmod m\)

\(m\) 为质数时,\(\varphi(m)=m-1\),代入上式得到费马小定理。

扩展欧拉定理

\[a^b\equiv\begin{cases}a^{b\bmod\varphi(m)},&\gcd(a,m)=1,\\a^b,&\gcd(a,m)\not = 1,b<\varphi(m),\\a^{(b\bmod\varphi(m))+\varphi(m)},&\gcd(a,m)\not =1,b\ge\varphi(m).\end{cases}\pmod m \]

Q. [SDOI2008] 仪仗队

将方阵看作一个左下角为 \((0,0)\),右上角为 \((N-1,N-1)\) 的平面直角坐标系。

容易发现 \((1,0),(0,1),(1,1)\) 是一定能被看到的,除此以外一个点 \((x,y)\) 能被看到当且仅当 \(1\le x,y\le N-1,x\not = y\)\(\gcd(x,y)=1\)

不难发现能看到的点关于直线 \(y=x\) 对称。所以我们考虑一半,即 \(1\le x\lt y\le N-1\)

也就是说,对于每一个 \(2\le y\lt N-1\),需要统计满足 \(1\le x\lt y\)\(\gcd(x,y)=1\)\(x\) 的数量,即 \(\varphi(y)\)

于是答案即为 \(3+2\times \sum_{i=2}^{N-1}\varphi(i)\)

实现上可以用埃氏筛一次性求出 \(2\sim N-1\) 中每一个数的欧拉函数。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long
const int maxn = 40000 + 10;

int n, phi[maxn];

void euler(int n)
{
    for (int i = 2; i <= n; i++)
        phi[i] = i;
    for (int i = 2; i <= n; i++)
        if (phi[i] == i)
            for (int j = i; j <= n; j += i)
                phi[j] = phi[j] / i * (i - 1);
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    if (n == 1)
        cout << 0 << endl;
    else
    {
        euler(n);
        int sum = 0;
        for (int i = 2; i <= n - 1; i++)
            sum += phi[i];
        cout << 3 + 2 * sum << endl;
    }
    return 0;
}

R. 给定 \(N\),求 \(\sum_{i=1}^{N}\gcd(i,N)\)\(0\lt n\le 2^{32}\)

\(\gcd(i,n)=d\),则 \(\gcd\left(\dfrac{i}{d},\dfrac{n}{d}\right)=1\)

显然 \(d\)\(n\) 的因数,对于每一个 \(d\),求有多少 \(i\) 使得 \(\gcd\left(\dfrac{i}{d},\dfrac{n}{d}\right)=1\)。假设有 \(x\) 个这样的 \(i\),答案即为 \(x\times d\)

而因为 \(\gcd\left(\dfrac{i}{d},\dfrac{n}{d}\right)=1\),显然 \(\dfrac{i}{d}\)\(\dfrac{n}{d}\) 互质,那么 \(x=\varphi(\dfrac{n}{d})\)

于是枚举每一个 \(n\) 的因子 \(d\),求出 \(\varphi(\dfrac{n}{d})\) 并累加。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long

int n, ans;

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= sqrt(n); i++)
        if (n % i == 0)
        {
            ans += phi(n / i) * i;
            if (i != sqrt(n))
                ans += phi(i) * (n / i);
        }
    cout << ans << endl;
    return 0;
}

S. 给定 \(L\),问至少多少个 \(8\) 连在一起组成的数是 \(L\) 的倍数。\(1\le L\le 2\times 10^9\)

直接推式子。

\[8\times \dfrac{10^n-1}{9}=kL \]

\[10^n-1 = \dfrac{9kL}{8} \]

\[10^n-1 = \dfrac{9k\times \dfrac{L}{\gcd(8,L)}}{\dfrac{8}{\gcd(8,L)}} \]

\(\dfrac{L}{\gcd(8,L)} = A,\dfrac{8}{\gcd(8,L)}=B\) 显然有 \(\gcd(A,B)=1\),此时有

\[10^n-1 = \dfrac{9kA}{B} \]

右边的值为整数 那么 \(k\)\(B\) 的倍数。

两边同时 \(\bmod{9A}\),得到 \(10^n-1\equiv 0\pmod{9A}\),即 \(10^n\equiv 1\pmod{9A}\)

由欧拉定理 \(a^{\varphi(m)}\equiv 1\pmod{m}\),若 \(a\)\(m\) 不互质,显然无解。

否则求 \(\varphi(9A)\),但不一定为最优解,遍历 \(\varphi(9A)\) 的因数。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long

int cas, l, mod;

int qmul(int a, int b)
{
    int res = 0;
    while (b > 0)
    {
        if (b & 1)
            res = (res + a) % mod;
        a = (a + a) % mod;
        b >>= 1;
    }
    return res;
}

int qpow(int a, int b)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = qmul(res, a) % mod;
        a = qmul(a, a) % mod;
        b >>= 1;
    }
    return res;
}

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

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    while (cin >> l && l)
    {
        mod = 9 * l / __gcd(8ll, 9 * l);
        if (__gcd(10ll, mod) != 1)
            cout << "Case " << ++cas << ":" << 0 << endl;
        else
        {
            int phi = euler(mod), ans = phi;
            for (int i = 1; i <= sqrt(phi); i++)
                if (phi % i == 0)
                {
                    if (qpow(10ll, i) == 1)
                        ans = min(ans, i);
                    if (qpow(10ll, phi / i) == 1)
                        ans = min(ans, phi / i);
                }
            cout << "Case " << ++cas << ":" << ans << endl;
        }
    }
    return 0;
}

T. 对于正整数 \(n\ge2\),记 \(F_n\) 为一组既约分数 \(\dfrac{a}{b}\),满足 \(0\lt a\lt b\le n\)\(\gcd(a,b)=1\)。给定 \(n\),计算 \(F_n\) 的项数。

观察样例发现分母取遍 \(2\sim n\)

对于每一个分母 \(x\),其对应的既约分数显然有 \(\varphi(x)\) 个。

于是考虑求出 \(2\sim n\) 的欧拉函数并直接求和。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long
const int maxn = 1'000'000 + 10;

int n, phi[maxn], sum[maxn];

void euler(int n)
{
    for (int i = 2; i <= n; i++)
        phi[i] = i;
    for (int i = 2; i <= n; i++)
        if (phi[i] == i)
            for (int j = i; j <= n; j += i)
                phi[j] = phi[j] / i * (i - 1);
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    euler(1'000'000);
    for (int i = 1; i <= 1'000'000; i++)
        sum[i] = phi[i] + sum[i - 1];
    while (cin >> n && n)
        cout << sum[n] << endl;
    return 0;
}

U. 给定 \(a,m,b\),求 \(a^b\bmod m\)\(1\le a\le 10^9,1\le b\le 10^{20000000},1\le m\le 10^8\)

暴力套公式,对 \(b\) 边读边模,记录是否需要加上 \(\varphi(m)\) 即可。

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long

int a, b, mod, phi;

int read()
{
    bool flag = false;
    int x = 0;
    char ch = getchar();
    while (!isdigit(ch))
        ch = getchar();
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        if (x >= phi)
            x %= phi, flag = true;
        ch = getchar();
    }
    if (flag)
        return x + phi;
    else
        return x;
}

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

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

signed main()
{
    cin >> a >> mod;
    phi = euler(mod);
    b = read();
    cout << qpow(a, b) << endl;
    return 0;
}

V.\(a_1^{a_2^{\dots^{a_n}}}\bmod 10007\) 的值。\(0\le a_i\lt 10^4,2\le n\le 1234567\)

\(f_x=a_x^{a_{x+1}^{\dots^{a_n}}}\),不难发现有 \(f_x=a_x^{f_{x+1}}\)

考虑自底向上递归应用扩展欧拉定理求出指数,递归边界即为 \(x\gt n\) 或此时模数为 \(1\)

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int __int128
const int maxn = 2000000 + 10;
const int mod = 10007;

int n, a[maxn], phi[maxn];
int m[maxn];

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

void write(int x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 10)
        write(x / 10);
    putchar(x % 10 + '0');
    return;
}

void euler(int n)
{
    for (int i = 2; i <= n; i++)
        phi[i] = i;
    for (int i = 2; i <= n; i++)
        if (phi[i] == i)
            for (int j = i; j <= n; j += i)
                phi[j] = phi[j] / i * (i - 1);
}

int qpow(int a, int b, int phi)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1)
        {
            res = res * a;
            if (res >= phi)
                res = res % phi + phi;
        }
        a = a * a;
        if (a >= phi)
            a = a % phi + phi;
        b >>= 1;
    }
    return res;
}

int DFS(int cur, int mod)
{
    if (cur == n + 1)
        return 1;
    if (mod == 1)
        return 0;
    return qpow(a[cur], DFS(cur + 1, phi[mod]), mod);
}

signed main()
{
    n = read();
    for (int i = 1; i <= n; i++)
        a[i] = read();
    euler(2000000);
    write((DFS(1, mod) + mod) % mod);
    return 0;
}

W. \(T\) 组数据,每次给出正整数 \(p\),求 \(2^{2^{2^{\dots}}}\bmod p\) 的值。

和上题思路一样,可以递归求解。

这里的指数 \(b=2^{2^{2^{\dots}}}\) 显然满足 \(b>\varphi(p)\),故可以直接应用 \(a^b\equiv a^{b\bmod\varphi(p)+\varphi(p)}\pmod p\)

递归边界显然是模数为 \(1\)

#include <bits/stdc++.h>
using namespace std;

#define endl '\n'

#define int long long
const int maxn = 10000000 + 10;

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

int qmul(int a, int b, int mod)
{
    int res = 0;
    while (b > 0)
    {
        if (b & 1)
            res = (res % mod + a % mod) % mod;
        a = (a % mod + a % mod) % mod;
        b >>= 1;
    }
    return res;
}

int qpow(int a, int b, int mod)
{
    int res = 1;
    while (b > 0)
    {
        if (b & 1)
            res = qmul(res, a, mod) % mod;
        a = qmul(a, a, mod) % mod;
        b >>= 1;
    }
    return res;
}

int DFS(int mod)
{
    if (mod == 1)
        return 0;
    return qpow(2, DFS(phi(mod)) + phi(mod), mod);
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int mod;
        cin >> mod;
        cout << (DFS(mod) + mod) % mod << endl;
    }
    return 0;
}

Merlin_Meow

2025/01/22

posted @ 2024-12-31 10:02  Merlin_Meow  阅读(231)  评论(0)    收藏  举报
Sakana Widget 自定义角色自适应示例