【科技】强制在线 O(1) 逆元
2026-01-09 22:02:01
发布于:山东
前置知识:Farey 序列以及其相关理论,根号平衡。
这里稍微提一下什么是 Farey 序列 以及其性质:
- 是第 阶的 Farey 序列,序列中存储所有不可约的分母 的值在 之间的分数(这里认为 也是不可约的分数)按照其值从小到大排序得到的有序分数序列。
- 性质 :对于 序列上任意两个相邻分数 ,在 序列上一定按照顺序连续的存在分数 。
- 推论 :对于 中任意两个相邻分数 ,必然有 。
- 性质 :。
- 性质 :对任意整数 和任意实数 ,一定可以在 序列中找到至少一个分数 ,满足 。
- 推论 :分数 一定是数 在序列 中向左查或者向右查能找到的第一个分数。
- 性质 :对于 序列内的每个分数 , 的值互不相同。
回到这个题目。考虑固定 ,记 。根据上面得到的性质和推论,可以知道此时必然存在一个分数 满足 。此时把不等式的左右两侧同时乘以 (显然有 ),得到:。
记 ,则此时又能得出两个结论:
- 。
考虑把上面提到的结论 改写,得到:。因为这里想要求的是 在模 意义下的逆元即 ,因此想到在同余式两侧同时乘以 ,得到:。
然后再利用结论 即 也就是 的绝对值很小,因此对这样的 只需在对应 Farey 序列上找出其对应的分数 ,计算 ,即可套用公式 解决。而此时 可以直接线性求逆元。
然而此时存在 的情况。对这个东西的处理是比较容易的,有 然后转化成上一种情况了。
问题在于怎么快速构造 阶 Farey 序列 。利用性质 可知 这个东西是 量级的,而利用性质 可以想到开桶做到 排序,因此构造 的总时间复杂度为 。
而现在还需要找出 阶 Farey 序列上一个分数 的前驱 / 后继分数,容易想到直接在值域上维护前缀和,然后简单处理即可 查询。
因此总时间复杂度分为两个部分:
- 线性递推 以内数的逆元,时间复杂度为 。
- 求 阶 Farey 序列并在上面做一些处理,时间复杂度为 。
因此总时间复杂度为 ,根号平衡一下就是 解得 ,此时时间复杂度为 。
代码实现
namespace Loyalty
{
int pre[N], idx, mod, n, m, Inv[N];
pair<int, int> frac[N], farey[N];
inline void init() { }
inline void nr_init(int p)
{
n = cbrt(p), m = n * n;
for (int i = 1; i < n; ++i)
for (int j = 0; j <= i; ++j)
{
int val = j * m / i;
if (!pre[val])
pre[val] = 1, frac[val] = {j, i};
}
for (int i = 1; i <= m; ++i)
pre[i] += pre[i - 1];
idx = 0;
for (int i = 0; i <= m; ++i)
if (frac[i].first || frac[i].second)
farey[idx++] = frac[i];
Inv[0] = Inv[1] = 1;
for (int i = 2; i <= p / n; ++i)
Inv[i] = 1ll * Inv[p % i] * (p - p / i) % p;
mod = p;
}
inline int inv(int a)
{
int val = 1ll * a * m / mod;
if (pre[val] < idx)
{
auto [x, y] = farey[pre[val]];
long long w = 1ll * a * y - 1ll * x * mod;
if (abs(w) <= mod / n)
{
int inv_t;
if (w >= 0)
inv_t = Inv[w];
else
inv_t = (mod - Inv[-w]) % mod;
return 1ll * y * inv_t % mod;
}
}
if (pre[val] - 1 >= 0)
{
auto [x, y] = farey[pre[val] - 1];
long long w = 1ll * a * y - 1ll * x * mod;
if (abs(w) <= mod / n)
{
int inv_t;
if (w >= 0)
inv_t = Inv[w];
else
inv_t = (mod - Inv[-w]) % mod;
return 1ll * y * inv_t % mod;
}
}
throw;
return -1;
}
}
int p, Q, op, ol;
namespace Your_Code
{
int inv[20][20];
inline void init()
{
for (int i = 2; i < 20; ++i)
for (int j = 1; j < i; ++j)
inv[i][j] = power(j, i - 2, i);
Loyalty::nr_init(p);
}
inline int solve(int x)
{
if (p < 20)
return inv[p][x];
return Loyalty::inv(x);
}
}
参考文献
全部评论 7
蒟蒻以严肃阅读后被看哭
2026-01-09 来自 浙江
1楼主依旧持续高产
2026-01-09 来自 浙江
0
ddd
2026-01-10 来自 山东
0蒟蒻已严肃看完后吓哭
2026-01-10 来自 上海
0%%%
2026-01-09 来自 上海
0


2026-01-09 来自 广东
0听老师说O(1)在线逆元没用(
2026-01-09 来自 广东
0确实没啥用()
2026-01-09 来自 山东
0那能不能讲点有用的线段树合并,球球了
2026-01-09 来自 广东
0那等我下个月考完期末再说()
2026-01-09 来自 山东
0
123123123
2026-01-09 来自 山东
0
































有帮助,赞一个