非常简易的还原分数方法

感谢 @unputdownable

问题简述

给定素数 p,正整数 x 满足 1x<p,则存在 abx(modp),即 abx(modp)。求一组 a,b 使得 max{|a|,||b} 尽可能小。

做法

考虑 a=bx+kp,则可以转写成 a=k(pmodx)(modx),即 ak(pmodx)。这是一个子问题,转化形式类似欧几里得算法。顶多迭代 logmin{a,b} 轮以后 x 就会变成 1,这时候停止。在每一层我们都能得到一个朴素解 x1x(modp),即 a=x,b=1;同时从下一层的某个解 k 还能还原出 唯一对应的 某个当前层的解 b=akpx。这样从下往上不断还原,在最顶层(即原问题)我们可以得到 logmin{a,b} 个不同的解。注意到实际上并不需要一步一步还原,因为 bx1a(modp),而 a 的数值是不会在传递中改变的,所以在每一层记录朴素解中 a 的数值(即当前的 x),在顶层求一次 x1modp,就能 Θ(logmin{a,b}) 地得到这些所有解。求出最小解不需要约分。

接下来证明这样就得到了所有正整数解(负数解一定对应某个正数解,不需要求)。考虑任何一组解,把它带入 a=bx+kp,求出 k,不断传递到下一层。a 在这个过程中始终不变,而在解变得没有意义(k=0)之前,b 一定会变成 1,这个时候就中止。那么从这一层开始向上传递求出的解就是这组解。所以所有解都可以被用这样的方式求出。

代码

LL kpow(LL x, LL k, LL p)
{
LL r = 1;
while (k)
{
if (k & 1) r = (__int128)r * x % p;
x = (__int128)x * x % p;
k >>= 1;
}
return r;
}
pair<LL, LL> recover(LL x, LL p)
{
vector<LL> a;
LL invx = kpow(x, p - 2, p), pp = p;
while (x)
{
a.push_back(x);
LL t = x;
x = p % x;
p = t;
}
pair<LL, LL> res{pp, pp};
for (auto ca : a)
{
LL cb = (__int128)ca * invx % pp;
ca = min(ca, pp - ca);
cb = min(cb, pp - cb);
if (max(res.first, res.second) > max(ca, cb))
res = {ca, cb};
}
return res;
}
posted @   kyEEcccccc  阅读(199)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示