// 扩展欧拉定理:大指数降幂 ll mul( ll a, ll b, ll mod ){ /*模板代码略*/ } ll qpow( ll a, ll k, ll mod ){ /*模板代码略*/ }
ll phi( ll n ){ ll res = n; for( ll i = 2; i * i <= n; ++ i ) { if( n % i == 0 ) { res = res / i * ( i - 1 ); while( n % i == 0 ) n /= i; } } if( n > 1 ) res = res / n * ( n - 1 ); return res; }
ll depow( const string &s, ll ph ){ ll res = 0; bool big = false; for( char ch : s ) { res = res * 10 + ch - '0'; if( res >= ph ) big = true, res %= ph; } return big ? res + ph : res; }
// 求 a^b mod mod,其中 b 为十进制字符串 ll solve( ll a, const string &b, ll mod ){ ll ph = phi( mod ); ll k = depow( b, ph ); returnqpow( a, k, mod ); }
4 阶与原根
4.1 元素的阶
设 gcd(a,m)=1。满足 ak≡1(modm) 的最小正整数 k,称为 a 模 m 的阶,记作 ordm(a)。
// 原根:判定存在性 / 最小原根 / 所有原根 ll mul( ll a, ll b, ll mod ){ /*模板代码略*/ } ll qpow( ll a, ll k, ll mod ){ /*模板代码略*/ }
ll phi( ll n ){ ll res = n; for( ll i = 2; i * i <= n; ++ i ) { if( n % i == 0 ) { res = res / i * ( i - 1 ); while( n % i == 0 ) n /= i; } } if( n > 1 ) res = res / n * ( n - 1 ); return res; }
// 原根存在当且仅当 n = 2, 4, p^k, 2p^k,p 为奇素数 boolexist( ll n ){ if( n == 2 || n == 4 ) returntrue; if( n < 2 ) returnfalse; if( n % 2 == 0 ) n /= 2; if( n % 2 == 0 ) returnfalse; for( ll p = 2; p * p <= n; ++ p ) { if( n % p == 0 ) { while( n % p == 0 ) n /= p; return n == 1; } } returntrue; }
vector<ll> factor_phi( ll n ){ vector<ll> fac; for( ll p = 2; p * p <= n; ++ p ) { if( n % p == 0 ) { fac.push_back( p ); while( n % p == 0 ) n /= p; } } if( n > 1 ) fac.push_back( n ); return fac; }
// 不存在返回 -1 ll min_root( ll n ){ if( !exist( n ) ) return-1; if( n == 2 ) return1; if( n == 4 ) return3; ll ph = phi( n ); vector<ll> fac = factor_phi( ph ); for( ll g = 2; g < n; ++ g ) { if( gcd( g, n ) != 1 ) continue; bool ok = true; for( ll p : fac ) { if( qpow( g, ph / p, n ) == 1 ) { ok = false; break; } } if( ok ) return g; } return-1; }
vector<ll> all_roots( ll n ){ ll g0 = min_root( n ); if( g0 == -1 ) return { }; ll ph = phi( n ), cur = 1; vector<ll> roots; for( ll k = 1; k <= ph; ++ k ) { cur = mul( cur, g0, n ); if( gcd( k, ph ) == 1 ) roots.push_back( cur ); } sort( roots.begin( ), roots.end( ) ); return roots; }
5 离散对数
5.1 问题形式
离散对数问题要求解
ax≡b(modm)
中的最小非负整数 x。
它与实数中的对数类似,但模意义下的幂具有周期,普通的取对数方法无法使用。
当 g 是模 m 的原根时,每个可逆剩余类都可以唯一写成 gx,于是模乘法可以转化为指数上的模加法。
// BSGS / ExBSGS:求 a^x = b (mod mod) ll mul( ll a, ll b, ll mod ){ /*模板代码略*/ } ll qpow( ll a, ll k, ll mod ){ /*模板代码略*/ }
ll exgcd( ll a, ll b, ll &x, ll &y ){ /*模板代码略*/ }
ll inv( ll a, ll mod ){ ll x, y; ll d = exgcd( a, mod, x, y ); if( d != 1 ) return-1; return ( x % mod + mod ) % mod; }
// 要求 gcd( a, mod ) = 1 ll bsgs( ll A, ll B, ll mod ){ A %= mod, B %= mod; if( mod == 1 || B == 1 ) return0; if( A == 0 ) return B == 0 ? 1 : -1; int t = ceil( sqrt( mod ) ); unordered_map<ll, ll> mp; ll cur = B; for( ll j = 0; j < t; ++ j ) { mp[cur] = j; cur = mul( cur, A, mod ); } ll At = qpow( A, t, mod ); cur = At; for( ll i = 1; i <= t + 1; ++ i ) { if( mp.count( cur ) ) return1 * i * t - mp[cur]; cur = mul( cur, At, mod ); } return-1; }
// 不要求 gcd( a, mod ) = 1 ll exbsgs( ll A, ll B, ll mod ){ A %= mod, B %= mod; if( B == 1 || mod == 1 ) return0; if( A == 0 ) return B == 0 ? 1 : -1; ll k = 0, C = 1, d; while( ( d = gcd( A, mod ) ) > 1 ) { if( B % d != 0 ) return-1; k ++, B /= d, mod /= d; C = mul( C, A / d, mod ); if( C == B ) return k; } ll t = ceil( sqrt( mod ) ); unordered_map<ll, ll> mp; ll cur = B; for( ll j = 0; j < t; ++ j ) { mp[cur] = j; cur = mul( cur, A, mod ); } ll At = qpow( A, t, mod ); cur = mul( C, At, mod ); for( ll i = 1; i <= t; ++ i ) { if( mp.count( cur ) ) return i * t - mp[cur] + k; cur = mul( cur, At, mod ); } return-1; }
6 二次剩余与 Cipolla
6.1 二次剩余
设 p 为奇质数。若同余方程
x2≡n(modp)
存在解,则称 n 是模 p 的二次剩余;否则称为二次非剩余。
当 n≡0(modp) 时,欧拉判别准则给出
n(p−1)/2≡{1(modp),−1(modp),n是二次剩余,n是二次非剩余.
若 x 是一个非零解,则另一个解是 p−x。
6.2 Cipolla 的核心构造
Cipolla 算法随机选择一个 a,使 w=a2−n 是模 p 的二次非剩余。
随后在扩域中引入满足 i2=w 的元素,并计算
(a+i)(p+1)/2.
结果的实部就是原方程的一个平方根。
随机选取的 w 约有一半是二次非剩余,因此通常只需要常数次尝试;主要复杂度来自扩域快速幂,为 O(logp)。
ll mul( ll a, ll b ){ return (ll)( (i128)a * b % P ); }
ll qpow( ll a, ll k ){ ll res = 1 % P; a %= P; for( ; k; k >>= 1, a = mul( a, a ) ) if( k & 1 ) res = mul( res, a ); return res; }
structComp { ll r, i; };
Comp mul( Comp a, Comp b ){ ll rr = ( mul( a.r, b.r ) + mul( mul( a.i, b.i ), w ) ) % P; ll ii = ( mul( a.r, b.i ) + mul( a.i, b.r ) ) % P; return Comp{ rr, ii }; }
Comp qpow( Comp a, ll k ){ Comp res = { 1, 0 }; for( ; k; k >>= 1, a = mul( a, a ) ) if( k & 1 ) res = mul( res, a ); return res; }
boolcheck( ll n ){ if( n == 0 ) returntrue; returnqpow( n, ( P - 1 ) / 2 ) == 1; }
// 无解返回 {-1,-1} pair<ll, ll> cipolla( ll n, ll p ){ P = p, n = ( n % P + P ) % P; if( n == 0 ) return { 0, 0 }; if( P == 2 ) return { n, n }; if( !check( n ) ) return { -1, -1 }; ll a; while( true ) { a = uniform_int_distribution<ll>( 0, P - 1 )( rng ); w = ( mul( a, a ) - n + P ) % P; if( qpow( w, ( P - 1 ) / 2 ) == P - 1 ) break; } ll x = qpow( { a, 1 }, ( P + 1 ) / 2 ).r; ll y = ( P - x ) % P; if( x > y ) swap( x, y ); return { x, y }; }