题解SP4948 Integer Factorization (29 digits)
Ruiqun2009 · · 题解
Miller-Rabin 素性检验算法
欧拉函数
欧拉函数定义为
不难得到,当
例如
缩系
在上述定义中,我们称
不难证明
欧拉定理
定理内容:
定理证明:
我们设集合
如果
两边同时乘以
下面是
于是我们假设
与
费马小定理
有了上面的欧拉定理,我们就有了费马小定理。
定理内容:
定理证明:由于
费马素性检验法
很遗憾,费马小定理的逆定理并不一定成立。但是它仍然可以筛掉很多合数。
所以我们可以随机选取多次
二次探测定理
如果
证明:移项然后平方差公式分解。
Miller 的研究
但是有一类数,你无论如何选取
卡迈克尔数有这么一条性质:如果
于是有了 Miller-Rabin 素性检验法。
算法内部逻辑
将
然后
- 如果答案是
p-1 则直接判定为素数。 - 如果答案不是
\pm 1 则直接判定为合数。
基于广义黎曼猜想,Miller 取的底数为
如果广义黎曼猜想成立则 Miller-Rabin 为目前时间复杂度最低的确定性素数判定算法(即能准确判断一个数是否是素数),时间复杂度为
Rabin 的研究
但是如果广义黎曼猜想不成立怎么办?(当然,检验这么多底数也没用)
于是 Rabin 想到了一种随机化算法:随机底数然后判定。这样的出错几率是
但是每次随机感觉太慢了,而且很容易就会出错。
有一天,数学家们发现了这样两个集合:
当然,
我更习惯用
代码:
// mint 改编自 hly1204 的 LongMontgomeryModInt
inline bool mr(mint x) {
const mint one(1), mone(-1);
if (x.pow(mone.val()) != one) return 0;
__uint128_t y = mone.val();
mint z(0);
while (!(y & 1)) {
y >>= 1;
z = x.pow(y);
if (z != one && z != mone) return false;
if (z == mone) return true;
}
return true;
}
inline bool prime(__uint128_t x) {
if (x < 2) return false;
if (x == 2 || x == 3 || x == 7 || x == 11 || x == 41 || x == 43 || x == 61) return true;
if (x % 2 == 0 || x % 3 == 0 || x % 7 == 0 || x % 11 == 0 || x % 41 == 0 || x % 43 == 0 || x % 61 == 0) return false;
mint::set_mod(x);
return mr(3) && mr(7) && mr(11) && mr(41) && mr(43) && mr(61);
}
Pollard \rho 算法
生日悖论
根据组合数可以得到只要一个房间里有
当
考虑一个问题,设置一个数据
随机数
我们设
再设数列
这样可以获得足够的随机性,并且根据生日悖论,只要生成
设
于是我们再设数列
再应用一次生日悖论可以得到时间复杂度为
Floyd 判环算法
假设两个人同时同向同地开始赛跑,A 的速度快,B 的速度慢,经过一定时间后,A 一定会追上 B,且 B 第一次被追上时 A 跑过的总距离减去 B 跑过的总距离一定是圈长的
于是我们可以写出这样的代码:
#ifdef __USE_MY_RAND__
static unsigned long long seed;
unsigned long long u64_hasher(unsigned long long x) {
return x *= 0x1952BB648B74B19Eull, x ^= x >> 47,
x *= 0x1952BB648B74B19Eull, x ^= x >> 47,
x * 0x1952BB648B74B19Eull;
}
unsigned long long rng() { return seed += u64_hasher(seed); }
#else
static std::mt19937_64 rng;
#endif
inline uint64_t rho(uint64_t value) {
if (value % 2 == 0) return 2;
if (value % 3 == 0) return 3;
if (value % 5 == 0) return 5;
if (value % 7 == 0) return 7;
mint::set_mod(value);
mint x, y;
mint c;
while (true) {
c = rng();
y = c;
x = c * c + c;
while (x != y) {
mint z((uint64_t)abs((__int128)y.val() - (__int128)x.val()));
if (uint64_t g = std::__gcd(z.val(), value); g > 1 && g < value) return g;
y = y * y + c;
x = x * x + c;
x = x * x + c;
}
}
}
倍增优化
上面的做法要调用很多次
其实这个
修改上述结论:假设两个人同时同向同地开始赛跑,A 的速度快,B 的速度慢,经过一定时间后,A 一定会追上 B,且此时 A 跑过的总距离减去 B 跑过的总距离一定是圈长的
我们每过一段时间将这些差值进行
如果某一时刻得到
更正:由拉梅定理得到
代码
用几个小素数判一下效率会有显著提升(?)。
附带 readu128()、putu128()。这份代码中采用倍增优化,
这里有一个小技巧:只改变
#ifdef __USE_MY_RAND__
static unsigned long long seed;
unsigned long long u64_hasher(unsigned long long x) {
return x *= 0x1952BB648B74B19Eull, x ^= x >> 47,
x *= 0x1952BB648B74B19Eull, x ^= x >> 47,
x * 0x1952BB648B74B19Eull;
}
unsigned long long rng() { return seed += u64_hasher(seed); }
#else
static std::mt19937_64 rng;
#endif
inline __uint128_t rho(__uint128_t value) {
if (value % 2 == 0) return 2;
if (value % 3 == 0) return 3;
if (value % 5 == 0) return 5;
if (value % 7 == 0) return 7;
mint::set_mod(value);
mint x, y, z, c, tmp;
uint64_t i, j;
__uint128_t g;
while (true) {
y = x = rng();
z = 1;
c = rng();
i = 0;
j = 1;
while (++i) {
x = x * x + c;
tmp = (x.val() > y.val()) ? (x - y) : (y - x);
z *= tmp;
if (x == y) break;
if (!z.val()) return tmp.val();
if (!(i & 32767) || i == j) {
g = std::__gcd(z.val(), value);
if (g > 1ull) return g;
if (i == j) {
y = x;
j <<= 1;
}
}
}
}
}
inline auto crack(__uint128_t value) {
struct _RetType { __uint128_t base; uint64_t power; };
std::vector<_RetType> ret;
if (prime(value)) {
ret.push_back({value, 1ul});
return ret;
}
_RetType node{2, 0};
while (value % 2 == 0) ++(node.power), value >>= 1;
if (node.power) ret.push_back(node);
auto dfs = [&](auto self, __uint128_t v) {
if (v <= 1) return;
if (!prime(v)) {
__uint128_t tmp = rho(v);
self(self, tmp);
self(self, v / tmp);
}
else {
auto pos = std::find_if(ret.begin(), ret.end(), [=](const auto& x) { return x.base == v; });
if (pos == ret.end()) ret.push_back({v, 1ul});
else ++(pos->power);
}
};
dfs(dfs, value);
std::sort(ret.begin(), ret.end(), [=](const auto& x, const auto& y) { return x.base < y.base; });
return ret;
}
inline __uint128_t readu128() {
__uint128_t ret = 0;
char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) ret = ret * 10 + (c & 15), c = getchar();
return ret;
}
inline void putu128(__uint128_t x) {
static char buf[45];
static int curpos;
curpos = 0;
do {
buf[curpos++] = x % 10;
x /= 10;
} while (x);
while (curpos--) putchar(buf[curpos] | 48);
}
题外话
这个 Pollard
我也是被这个 random_device 气疯了。在 LOJ 上一会 3000ms,一会 5600ms。
参考文献
- 数论四大定理之欧拉定理
- 素数 - OI-Wiki
- 分解素因数 - OI-Wiki