fsy's blog

燃峥嵘岁月,烈日终破云。

(补档)UVA 1457 Decrypt Messages

高次剩余问题:

给定正整数 $a, y, p$,其中 $p$ 是质数,求 $x \in [0, p)$ 满足:
$$
x^a \equiv y \pmod p
$$
此处的 $x$ 被称为一个高次剩余,有时也译作离散根(discrete root)

前置知识 - 原根:

定义 1(阶): 满足模方程 $a^d \equiv 1 \pmod p$ 的最小正整数 $d$ 被称为模 $p$ 意义下 $a$ 的一个阶(order),常用 $\textrm{ord}_p a$ 或 $\delta_p(a)$ 表示。

定理 1.1: $\textrm{ord}_p a | \varphi(p)$。

证明:令 $d = \textrm{ord}_p a$,假设 $d \not | \varphi(p)$,设 $\varphi(p) = \alpha \times d + b$,其中 $b \in [1, d)$。

由欧拉定理 $a^{\varphi(p)} \equiv 1 \pmod p$,故 $a^{\alpha \times d + b} \equiv 1 \pmod p$,可推得 $a^b \equiv 1 \pmod p$

由于 $b < d$ 且 $a^b \equiv 1 \pmod p$,故我们推出了一个更小的阶 $b$,出现矛盾,得证!

定理 1.2: 令模 $p$ 意义下 $a$ 的一个阶为 $d’$,则 $a^0 \bmod p, a^1 \bmod p, \cdots a^{d’-1} \bmod p$ 互不相同。

证明: 若存在 $i, j \in [0, d’)$ 使得 $i > j$,$a^i \equiv a^j \pmod p$,则移项后有 $a^{i-j} \equiv 1 \pmod p$。

由于 $i > j$ 故 $i-j>0$,且易证 $i-j < d’$,故我们找到了一个更小的阶 $i-j$,出现矛盾,得证!

定义 2(原根):若存在整数 $g \in [0, p)$,使得模 $p$ 意义下,$g$ 的阶等于 $\varphi(p)$,则称 $g$ 为模 $p$ 意义下的一个原根(primitive root)

下面不加证明的给出三个定理。

定理 2.1: 对于任何大于 $2$ 的质数 $p$ 与所有正整数 $e$,当 $n = 2, 4, p^e, 2p^e$ 时,存在模 $n$ 意义下的原根。

定理 2.2: 若存在模 $n$ 意义下的原根,则模 $n$ 大小不超过 $n$ 的原根有 $\varphi(\varphi(n))$ 个。

**定理 2.3: **$g^0 \pmod p, g^1 \pmod p, \cdots, g^{p-2} \pmod p$ 这 $p-1$ 个数互不相同。

事实上定理 2.3 = 定义 2 + 定理 1.2

关于离散对数,指标的定义说法不一,本文使用如下定义:

定义 3(离散对数): $p > 1$,正整数 $a, b \in [0, p)$,若存在 $k$ 使得 $a^k \equiv b \pmod p$,则称 $k$ 为模 $p$ 意义下到基 $a$ 上 $b$ 的一个离散对数(discrete logarithm)

定义 4(指标): 令 $p$ 原根为 $g$,$b \in [0, p)$,则指标(index)为模 $p$ 意义下到基 $g$ 上 $b$ 的离散对数,记作 $\textrm{ind}_g b$。

容易看出离散对数,指标可以使用 Shank 的大步小步算法计算。

怎么求原根?

设 $\varphi(p)$ 的唯一分解式为 ${p_1}^{x_1} {p_2}^{x_2} \cdots p_{cnt}^{x_{cnt}}$。

则由原根的定义,可知:$\forall i \in [1, cnt]$,$g^{\frac{\varphi(p)}{p_i}} \not\equiv 1 \pmod p$

质因数分解 $\varphi(p)$ 后,暴力枚举 $g$ 即可。

原根的密度很大,最小原根一般很小,所以不必特别担心复杂度。

怎么求模质数意义下的高次剩余?

重新审视这个式子。
$$
x^a \equiv y \pmod p
$$
注意到 $p$ 是质数,模 $p$ 意义下,一定有原根 $g$。

由性质 2.3 可知,$g^0 \pmod p, g^1 \pmod p, \cdots, g^{p-2} \pmod p$ 这 $p-1$ 个数互不相同。

设 $x \equiv g^{\lambda} \pmod p$,$y \equiv g^{\beta} \pmod p$,则有:
$$
g^{a\lambda} \equiv g^{\beta} \pmod p
$$
注意到 $\beta$ 可以使用大步小步算法求出。

由性质 2.3 可知 $a\lambda \equiv \beta \pmod {p-1}$

证明: 设 $a \lambda - \beta \equiv k \pmod {p-1}$,其中 $k \in [1, p-2]$。

则 $g^{a\lambda} \equiv g^{\beta} \pmod p \rightarrow g^{k} \equiv 1 \pmod p$

由性质 2.3 可知,$g^0 \pmod p, g^1 \pmod p, \cdots, g^{p-2} \pmod p$ 这 $p-1$ 个数互不相同。因为 $g^0 \equiv 1 \pmod p$,且 $k \neq 0$,可知 $g^k \not \equiv 1 \pmod p$,出现矛盾!得证!

注意到上式是一个不定方程,可以使用扩展欧几里得算法求解出 $\lambda$。

最后使用快速幂计算 $x = g^{\lambda} \bmod p$ 即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define LD long double
#define reg register
#define _rep(i, x, y) for(int i = x; i <= y; i++)
#define _per(i, x, y) for(int i = x; i >= y; i--)
template <typename T>
inline void read(T &x) {
x = 0; T f = (T) 1;
char c = getchar();
for(; !isdigit(c); c = getchar()) {if(c == '-') f = -f;}
for(; isdigit(c); c = getchar()) x = x * 10 + c - 48;
x *= f;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
read(x); read(args...);
}
template <typename T>
inline void write(T x) {
if(x < 0) {x = -x; putchar('-');}
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T>
inline void writesp(T x, char sp = ' ') {write(x); putchar(sp);}

const int mod = 1e9 + 7;
int pow_mod(int a, int b, int p = mod) {
int sum = 1;
while(b) {
if(b & 1) sum = 1ll * sum * a % p;
a = 1ll * a * a % p; b >>= 1;
}
return sum;
}
int ex_gcd(int a, int b, LL &x, LL &y) {
if(!b) {x = 1; y = 0; return a;}
int d = ex_gcd(b, a % b, x, y);
LL z = x; x = y; y = z - (a / b) * y; return d;
}
int inv(int a, int p = mod) {
LL x, y; int d = ex_gcd(a, p, x, y);
return (d == 1 ? (x + p) % p : 1);
}
std::vector<int> equation(int a, int b, int p = mod) {
std::vector<int> solution;
LL x, y; int d = ex_gcd(a, p, x, y);
if(b % d) return solution;
int m = p; a /= d; b /= d; m /= d;
LL sol = 1ll * b % m * inv(a, m) % m;
_rep(i, 0, d - 1) solution.push_back((1ll * m * i + sol) % p);
return solution;
}
int bsgs(int a, int b, int p = mod) {
std::unordered_map<int, int> HashTable;
int blk = ceil(sqrt(p)), ret = b, qwq; HashTable[b] = 0;
_rep(i, 1, blk) ret = 1ll * ret * a % p, HashTable[ret] = i;
ret = 1, qwq = pow_mod(a, blk, p);
_rep(i, 1, blk) {
ret = 1ll * ret * qwq % p;
if(HashTable.count(ret)) return (i * blk - HashTable[ret] + p) % p;
}
return -1;
}
int primitive(int p = mod) {
int phi = p - 1, tmp = phi; std::vector<int> factors;
_rep(i, 2, sqrt(phi)) {
if(!(tmp % i)) {
factors.push_back(i);
while(!(tmp % i)) tmp /= i;
}
}
if(tmp > 1) factors.push_back(tmp);
int g = 1;
while(1) {
bool is_g = 1;
for(auto i : factors) is_g = is_g & (pow_mod(g, phi / i, p) != 1);
if(is_g) break;
g++;
}
return g;
}
std::vector<int> mod_root(int a, int b, int p = mod) {
std::vector<int> Sol;
if(!b) {Sol.push_back(0); return Sol;}
int g = primitive(p), beta = bsgs(g, b, p);
if(beta == -1) return Sol;
Sol = equation(a, beta, p - 1);
if(Sol.empty()) return Sol;
_rep(i, 0, Sol.size() - 1) Sol[i] = pow_mod(g, Sol[i], p);
std::sort(Sol.begin(), Sol.end()); return Sol;
}

int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool leap_year(int y) {return (y % 400 == 0 || (y % 100 != 0 && y % 4 == 0));}
bool leap_second(int y, int m = 12) {return (y % 10 == 5 || y % 10 == 8) && (m == 12);}
void print_time(int y, int m, int d, int H, int M, int S) {
printf("%d.%02d.%02d %02d:%02d:%02d\n", y, m, d, H, M, S);
}
void transfer(int res) {
int y = 2000, m = 1;
while(1) {
int ds = (365 + leap_year(y)) * 86400 + leap_second(y, 12);
if(res < ds) break;
res -= ds; y++;
}
while(1) {
int ms = (days[m] + (leap_year(y) && m == 2)) * 86400 + leap_second(y, m);
if(res < ms) break;
res -= ms; m++;
}
if(leap_second(y, m) && res == 31ll * 24 * 60 * 60) {
print_time(y, m, 31, 23, 59, 60);
} else {
print_time(y, m, res / 86400 + 1, res % 86400 / 3600, res % 3600 / 60, res % 60);
}
}
int main() {
int T, t = 0; read(T);
while(T--) {
int p, q, a; read(p, q, a); printf("Case #%d:\n", ++t);
std::vector<int> Roots = mod_root(q, a, p);
if(!Roots.size()) {puts("Transmission error");}
else {
for(auto i : Roots) transfer(i);
}
}
return 0;
}