这几天沉迷在数论的海洋中(快淹死了)无法自拔……
首先高次不定方程分为\(A ^ x \equiv B(mod \ \ C)\)和\(x ^ A \equiv B(mod \ \ C)\)两种形式,对应的解法也不一样。今天先学了第一个。
\(A ^ x \equiv B(mod \ \ C)\)
首先得知道这么一回事儿,就是这个解是有周期的,且最大周期为\(C\)。
证明:由于\(A ^ x \ \ mod \ \ C < C\),所以最多只有\(C\)个元素,那么在\(n(n \leqslant C)\)之内,如果有\(A ^ {x_0} \equiv A ^ {x_0 + n} (mod \ \ C)\),那么\(A ^ {x_0} A ^ t \equiv A ^ {x_0 + n} A ^ t(mod \ \ C)\),既有\(A ^ x \equiv A ^ {x + n}\)对于所有\(x\)成立,且周期为\(n\)。
那么暴力的算法就很好写了:从\(0\)到\(C - 1\)枚举,代入方程看是否成立。
但是这个复杂度是\(O(maxint * log(maxint))\)的,过不去。
于是就有了很奇葩的\(Baby-step\) \(giant-step(BSGS)\)算法。能使时间复杂度达到\(O(\sqrt{n})\)。
既然是\(O(\sqrt{n})\),那自然想到了分块,也就是我们把枚举的数分成\(\sqrt{C}\)块,每一块里有\(\sqrt{C}\)
个数。但这样还是\(O(C)\)复杂度的,下面才是重点:
枚举的时候看成\(A ^ {im - y} \equiv B(mod \ \ C)\),其中\(i\)代表第几块,\(m\)是块的大小,然后在每一块内枚举\(y\)。
然后变个型:\(A ^ {im} \equiv A ^ y B (mod \ \ C)\)。这也就是说组数\(i\)和每一组内的\(y\)并没有关系,因此我们可以先从\(0 ~ \sqrt{C} - 1\)枚举\(y\),将答案存在哈希表中(可\(O(1)\)查询),然后在\(O(\sqrt{n})\)枚举块数,看哈希表中有没有这个答案即可。
但上述思路的前提是在\(A\)和\(C\)互质的情况下。若\(A\)和\(C\)不互质,只要刚开始把\(A\)和\(C\)都不断除以\(gcd(A, C)\),直到\(A\)和\(C\)互质为止,然后再像上面做即可。(这好像就是传说中的\(exbsgs\))
然而哈希表我不太会写,用\(map\)又gg了,所以这里贴了一个别人的代码……
题目链接
#include <cmath>
#include <cstdio>
int X, Y, Z, K;
int Gcd(int a, int b) { return !b ? a : Gcd(b, a % b); }
int exGcd(int a, int b, int &x, int &y) {
if (b == 0) return x = 1, y = 0, a;
int r = exGcd(b, a % b, y, x);
y -= (long long)(a / b) * x;
return r;
}
int inv(int a, int m) {
int x, y;
exGcd(a, m, x, y);
return (x % m + m) % m;
}
namespace Hash {
const int N = 50000;
const int H = 999979;
int tot, adj[H], nxt[N], num[N], val[N];
int top, stk[N];
void init() {
tot = 0;
while (top) adj[stk[top--]] = 0;
}
void insert(int x, int y) {
int h = x % H;
for (int e = adj[h]; e; e = nxt[e]) {
if (num[e] == x) {
val[e] = y;
return ;
}
}
if (!adj[h]) stk[++top] = h;
nxt[++tot] = adj[h], adj[h] = tot;
num[tot] = x, val[tot] = y;
}
int query(int x) {
int h = x % H;
for (int e = adj[h]; e; e = nxt[e])
if (num[e] == x) return val[e];
return -1;
}
}
// a^x = b (mod c)
int BSGS(int a, int b, int c) {
int cnt = 0, G, d = 1;
while ((G = Gcd(a, c)) != 1) {
if (b % G != 0) return -1;
++cnt, b /= G, c /= G;
d = (long long)d * (a / G) % c;
}
b = (long long)b * inv(d, c) % c;
Hash::init();
int s = sqrt(c);
int p = 1;
for (int i = 0; i < s; ++i) {
if (p == b) return i + cnt;
Hash::insert((long long)p * b % c, i);
p = (long long)p * a % c;
}
int q = p, t;
for (int i = s; i - s + 1 <= c - 1; i += s) {
t = Hash::query(q);
if (t != -1) return i - t + cnt;
q = (long long)q * p % c;
}
return -1;
}
bool check() {
for (int i = 0, j = 1; i <= 10; ++i) {
if (j == K) {
printf("%d\n", i);
return true;
}
j = (long long)j * X % Z;
}
if (X == 0) {
puts("No Solution");
return true;
}
return false;
}
int main() {
// X^Y = K (mod Z)
while (scanf("%d%d%d", &X, &Z, &K), (long long)X + Z + K > 0){
X %= Z, K %= Z;
if (check()) continue;
int ans = BSGS(X, K, Z);
if (ans == -1) puts("No Solution");
else printf("%d\n", ans);
}
return 0;
}