求x满足 ab≡x(modp) , 即求 x=abmodp
快速幂。。。
LL pow_mod(LL a, LL b, LL p) {
LL r = 1; a %= p;
while(b) {
if(b&1) r = (r*a) % p;
a = (a*a) % p;
b >>= 1;
}
return r;
}
求x满足
ax≡c(modp)
使用BSGS算法即可 =>Baby Step Giant Step(好奇怪的名字)及其扩展: 求离散对数
求 x 满足
xa≡b(modp)
难点+重点!!!
先讨论p为素数的情况
设g为p的一原根,记k关于g的离散对数(mod p)为 indgk ,则有
a∗indgx≡indgb(modϕ(p))
设 t1=indgb , 则有gt1≡b(modp)
那么 t1 即与情形2一致,可由BSGS算法求出 t1 。
设 t2=indgx , 则原式变为a∗t2≡t1(modϕ(p))
其中只有 t2 是未知的, 可由扩展欧几里德求出所有 t2 的值。
又由 t2=indgx 知gt2≡x(modp)
与情形1一致,可由快速幂求出所有x值。
至此解决p为素数时的问题。
结合一道题目理解
Broot HDU - 3930
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("ot.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 1e6 + 100;
const LL INF = 0x7fffffff;
const int dir[5][2] = {0,0,-1,0,1,0,0,-1,0,1};
const LL MOD = 1e9+7;
const double eps = 1e-6;
LL a, b, p;
//工具
bool is[maxn]; LL prm[maxn], id;
LL getprm(LL n) {
if(n == 1) return 0;
LL k = 0; met(is, 1);
is[0] = is[1] = 0;
for(LL i = 2; i < n; ++i) {
if(is[i]) prm[k++] = i;
for(LL j = 0; j < k && (i*prm[j] < n); ++j) {
is[i*prm[j]] = 0;
if(i % prm[j] == 0) break;
}
}
return k;
}
LL Euler(LL x) { //素数的欧拉函数
return x-1;
}
LL gcd(LL a, LL b){
return b ? gcd(b, a%b) : a;
}
LL extgcd(LL a, LL b, LL& x, LL& y) {
if (b == 0) { x=1; y=0; return a; }
LL d = extgcd(b, a % b, x, y);
LL t = x; x = y; y = t - a / b * y;
return d;
}
//快速乘 -- a*b % mod
LL pow_mul(LL a, LL b, LL p) {
LL r = 0; a %= p;
while(b) {
if(b&1) r = (r+a) % p;
a = (a+a) % p;
b >>= 1;
}
return r;
}
LL pow_mod(LL a, LL b, LL p) {
LL r = 1; a %= p;
while(b) {
if(b&1) r = pow_mul(r, a, p);
a = pow_mul(a, a, p);
b >>= 1;
}
return r;
}
//求原根
LL fac[maxn], num[maxn], tot;
LL Factor(LL n){
LL ans = 1, temp = n; tot = 0;
for (LL i = 0; i < id && prm[i] * prm[i] <= temp; i++){
if (n % prm[i] == 0){
fac[tot] = prm[i], num[tot] = 0;
while (n%prm[i] == 0) n /= prm[i], ++num[tot];
ans *= (num[tot] + 1);
++tot;
}
}
if (n != 1){
fac[tot] = n, num[tot] = 1;
ans *=(num[tot]+1);
++tot;
}
return ans;
}
LL root(LL p) {
LL phi = Euler(p);
Factor(phi);
for(LL g = 2; ; g++) {
bool f = 1;
for(int i = 0; i < tot; ++i) {
LL t = phi / fac[i];
if(pow_mod(g, t, p) == 1) { f = 0; break; }
}
if(f) return g;
}
}
//BSGS
LL BSGS(LL a, LL b, LL p) {
a %= p; b %= p;
map h;
LL m = ceil(sqrt(p)), x, y, d, t = 1, v = 1;
for(LL i = 0; i < m; ++i) {
if(h.count(t)) h[t] = min(h[t], i);
else h[t] = i;
t = pow_mul(t, a, p);
}
for(LL i = 0; i < m; ++i) {
d = extgcd(v, p, x, y);
x = (x* b/d % p + p) % (p);
if(h.count(x)) return i*m + h[x];
v = pow_mul(v, t, p);
}
return -1;
}
//求模线性方程
LL modeq(LL a, LL b, LL p, LL r[]) {
LL e, i, d, x, y;
d = extgcd(a, p, x, y);
if (b % d) { return -1; }
e = (x * (b / d) + p) % p;
for (i = 0; i < d; i++) {
r[i] = (e + i*(p/d) + p) % p;
}// 总共 (a, m) 个解
return d;
}
//开始解决问题
LL solve(LL a, LL b, LL p, LL r[], LL ans[]) {
LL g = root(p);
LL t1 = BSGS(g, b, p);
LL phi = Euler(p);
LL cnt = modeq(a, t1, phi, r);
if(cnt == -1) return -1;
for(int i = 0; i < cnt; ++i) {
ans[i] = pow_mod(g, r[i], p);
}
return cnt;
}
LL ans[maxn], res[maxn];
int main() {
#ifdef _LOCAL
IN; //OT;
#endif // _LOCAL
id = getprm(maxn-1); LL kase = 0;
while(scanf("%lld%lld%lld", &a, &p, &b) == 3) {
LL cnt = solve(a, b, p, res, ans);
printf("case%lld:\n", ++kase);
if(cnt == -1) { printf("-1\n"); continue; }
sort(ans, ans + cnt);
for(int i = 0; i < cnt; ++i) printf("%lld\n", ans[i]);
}
return 0;
}
p为合数的情况
这种情况有点复杂,不过大体可分三部分解决问题,建立在前面的基础之上,就容易理解一些。
首先将 p 分解素因子为
p=pe11pe22⋯pekk
那么我们对每一个 i 属于 [1,⋯,k] ,都有xa≡b(modpeii)
我们可利用上面的知识去解出这里的x,不过由于模数不一定为素数的原因, 不能单纯地使用上面p为素数的方法去解,关于这个式子的解法是解决整个问题的关键,下面会给出细节,此处暂且认为已经解出x的一组解 {xi1,xi2,⋯,xiti} 。
那么上面k个式子就会有k组这样的解, 我们从每组里取一个x值出来,记为 {X1,X2,⋯,Xk} ,那么如果我们能找到一个Z满足⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪z≡X1(modpe11)z≡X2(modpe22)⋮z≡Xk(modpekk)
那么 Z 必为原式的一个解。
而要解这个,只需使用CRT即可, 同时我们需要一个dfs来枚举所有X可能的组合。
至此解决问题。
下面来看如何解决 xa≡b(modpeii) 的解的问题。
首先,我们回想上面 p 为素数的情况,在整个操作中我们用到了 p 的原根 g ,和 b 与 x 关于 g 的离散对数(mod p)。
换句话数,如果p具有原根g,且b和x有关于g的离散对数(mod p)。那么p是不是素数就都能用上面的方法去做了。
这也是我们要分三种情况去讨论的依据。首先来看第一种情况, pi 没有原根的情况。
我们知道, 一个数m有原根的充要条件是 m=1,2,4,pe,2pe , 其中p为奇素数, e为正整数。那么只有 pi=2 的时候, peii 才没有原根,所以当 pi=2 的时候, 我们不能像上面那样处理。
一个有效的方法是,直接枚举 [0,⋯,peii] 之间的 x , 将满足上式的x存入结果中即可。第二种情况,b没有关于g的离散对数(mod p)。
容易理解,只有0没有关于g的离散对数,那么如果 b≡0(modpeii) ,则b无离散对数,不能用上面的方法处理。
这个时候, 我们肯定可以找一到一个最小的 t ,满足(pti)a≡b≡0(modpeii)
所以上式的解一定为 x=0,pti,2pti,3pti,⋯ , 且总共有 peiipti 个解。最后一种情况。
除了上面两种以外,剩下的就可以按照p为素数时的方式处理了。
结合一道题目理解
X^A Mod B 51Nod - 1123
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define met(a,b) memset(a, b, sizeof(a));
#define IN freopen("in.txt", "r", stdin);
#define OT freopen("ot.txt", "w", stdout);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int maxn = 1e6 + 100;
const LL INF = 0x7fffffff;
const int dir[5][2] = {0,0,-1,0,1,0,0,-1,0,1};
const LL MOD = 1e9+7;
const double eps = 1e-6;
LL a, b, p;
bool is[maxn]; LL prm[maxn], id;
LL getprm(LL n) {
if(n == 1) return 0;
LL k = 0; met(is, 1);
is[0] = is[1] = 0;
for(LL i = 2; i < n; ++i) {
if(is[i]) prm[k++] = i;
for(LL j = 0; j < k && (i*prm[j] < n); ++j) {
is[i*prm[j]] = 0;
if(i % prm[j] == 0) break;
}
}
return k;
}
/*
LL Euler(LL x) { //素数的欧拉函数
return x-1;
}*/
LL Euler(LL x) {
LL ans = x, m = (LL)sqrt(x*1.0)+1;
for(LL i = 2; i < m; ++i) if(x%i == 0) {
ans = ans / i * (i-1);
while(x%i == 0) x /= i;
}
if(x > 1) ans = ans / x * (x-1);
return ans;
}
LL gcd(LL a, LL b){
return b ? gcd(b, a%b) : a;
}
LL extgcd(LL a, LL b, LL& x, LL& y) {
if (b == 0) { x=1; y=0; return a; }
LL d = extgcd(b, a % b, x, y);
LL t = x; x = y; y = t - a / b * y;
return d;
}
//快速乘 -- a*b % mod
LL pow_mul(LL a, LL b, LL p) {
LL r = 0; a %= p;
while(b) {
if(b&1) r = (r+a) % p;
a = (a+a) % p;
b >>= 1;
}
return r;
}
LL pow_mod(LL a, LL b, LL p) {
LL r = 1; a %= p;
while(b) {
if(b&1) r = pow_mul(r, a, p);
a = pow_mul(a, a, p);
b >>= 1;
}
return r;
}
LL Factor(LL n, LL fac[], LL num[], LL& tot){
LL ans = 1, temp = n; tot = 0;
for (LL i = 0; i < id && prm[i] * prm[i] <= temp; i++){
if (n % prm[i] == 0){
fac[tot] = prm[i], num[tot] = 0;
while (n%prm[i] == 0) n /= prm[i], ++num[tot];
ans *= (num[tot] + 1);
++tot;
}
}
if (n != 1){
fac[tot] = n, num[tot] = 1;
ans *=(num[tot]+1); //n的素因数中最多只有一个大于根号n的;
++tot;
}
return ans;
}
LL fac[maxn], num[maxn];
LL root(LL p, LL phi) {
//LL phi = Euler(p);
LL tot;
Factor(phi, fac, num, tot);
for(LL g = 2; ; g++) {
bool f = 1;
for(int i = 0; i < tot; ++i) {
LL t = phi / fac[i];
if(pow_mod(g, t, p) == 1) { f = 0; break; }
}
if(f && pow_mod(g, phi, p) == 1) return g;
}
return -1;
}
LL modeq(LL a, LL b, LL p, LL r[]) {
LL e, i, d, x, y;
d = extgcd(a, p, x, y);
if (b % d) { return -1; }
e = (x * (b / d) + p) % p;
for (i = 0; i < d; i++) {
r[i] = (e + i*(p/d) + p) % p;
}// 总共 (a, m) 个解
return d;
}
LL CRT(LL a[], LL m[], LL k) {
LL i, d, x, y, Mi, ans = 0, M = 1;
for (i = 0; i < k; i++) M *= m[i]; // ! 注意不能overflow
for (i = 0; i < k; i++) {
Mi = M / m[i];
d = extgcd(m[i], Mi, x, y); // y 为逆元 -- Mi*y === 1 (% m[i])
ans = (ans + a[i]*y*Mi) % M;
}
if (ans >= 0) return ans;
else return (ans + M);
}
LL exBSGS(LL a, LL b, LL p) {
a = (a%p+p)%p; b = (b%p+p)%p;
LL ret = 1;
for(LL i = 0; i <= 50; ++i) {
if(ret == b) return i;
ret = (ret*a) % p;
}//枚举比较小的i
LL x,y,d, v = 1, cnt = 0;
while((d = gcd(a, p)) != 1) {
if(b % d) return -1;
b /= d, p /= d;
v = (v * (a/d)) % p;
++cnt;
}//约分直到(a, p) == 1
map h;
LL m = ceil(sqrt(p)), t = 1;
for(LL i = 0; i < m; ++i) {
if(h.count(t)) h[t] = min(h[t], i);
else h[t] = i;
t = (t*a) % p;
}
for(LL i = 0; i < m; ++i) {
d = extgcd(v, p, x, y);
x = (x* (b/d) % p + p) % p;
if(h.count(x)) return i*m + h[x] + cnt;
v = (v*t) % p;
}
return -1;
}
LL F[maxn], N[maxn], P[maxn], E[maxn], r[maxn], tot;
vector< vector > ans;
vector ot;
LL A[maxn], M[maxn];
void dfs(LL dep, LL N){
if( dep == N ){ ot.push_back(CRT(A, M, N)); }
else {
for(LL i = 0; i < ans[dep].size(); ++i) {
A[dep] = ans[dep][i];
dfs(dep+1, N);
}
}
}
LL solve(LL a, LL b, LL p, LL r[]) {
//分解因式
Factor(p, F, N, tot);
for(LL i = 0; i < tot; ++i) {
P[i] = pow_mod(F[i], N[i], p*2);
E[i] = P[i] - P[i]/F[i];
}
ans.clear();
for(LL i = 0; i < tot; ++i) {
vector res;
if(F[i] == 2) {
LL tb = (b%P[i]+P[i])%P[i];
for(LL j = 0; j < P[i]; ++j) {
if(pow_mod(j, a, P[i]) == tb) res.push_back(j);
}
if(res.size() == 0) return -1;
sort(res.begin(), res.end());
res.erase(unique(res.begin(), res.end()), res.end());
ans.push_back(res);
continue;
}
if(b % P[i] == 0) {
LL x = 0, ret = 1;
while(pow_mod(ret, a, P[i]) != 0) ret *= F[i];
for(int j = 0; j < P[i]/ret; ++j) res.push_back(ret*j);
sort(res.begin(), res.end());
res.erase(unique(res.begin(), res.end()), res.end());
ans.push_back(res);
continue;
}
LL tp = P[i], tb = b%tp,te = E[i];//, d = gcd(tp, tb);
LL g = root(tp, te); if(g == -1) return -1; //求原根
LL t1 = exBSGS(g, tb, tp); if(t1 == -1) return -1; //求离散对数
LL cnt = modeq(a, t1, te, r); if(cnt == -1) return -1;//求log_gX
for(LL j = 0; j < cnt; ++j) {
res.push_back(pow_mod(g, r[j], tp));
}
sort(res.begin(), res.end());
res.erase(unique(res.begin(), res.end()), res.end());
ans.push_back(res);
}
//CRT合并
ot.clear();
for(LL i = 0; i < tot; ++i) M[i] = P[i];
dfs(0, tot);
sort(ot.begin(), ot.end());
ot.erase(unique(ot.begin(), ot.end()), ot.end());
}
int main() {
#ifdef _LOCAL
IN; //OT;
#endif // _LOCAL
id = getprm(maxn-1);
int t; cin >> t;
while(t--) {
scanf("%lld%lld%lld", &a, &p, &b);
if(solve(a, b, p, r) == -1) printf("No Solution\n");
else {
for(LL i = 0; i < ot.size(); ++i) printf("%lld ", ot[i]);
printf("\n");
}
}
return 0;
}
还有一道练手题
God of Number Theory HDU - 3731