解题报告 数论 2024/12/28 ~ 2025/01/22
分班了,现在来还欠下的债。
算术基本定理
内容
任何一个大于 \(1\) 的正整数都能唯一分解成有限个质数的乘积,可写作:
其中 \(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\)。
推式子。
然后就不会了,但是注意到 \(x,y\ge n!\),不妨设 \(x=n!+a, y=n!+b\) 并继续化简。
因为每一组 \((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;
}
注意到 \(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;
}
观察到每次洗牌后第 \(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!)\)。
显然在 \(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\),代入上式得到费马小定理。
扩展欧拉定理
将方阵看作一个左下角为 \((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\)。
直接推式子。
记 \(\dfrac{L}{\gcd(8,L)} = A,\dfrac{8}{\gcd(8,L)}=B\) 显然有 \(\gcd(A,B)=1\),此时有
右边的值为整数 那么 \(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

浙公网安备 33010602011771号