\(a \cdot x = b \pmod n\)
线性同余方程
题解
法1.n,a互质时使用逆元,否则两边除掉gcd(a,x)然后逆元。(如果b除不尽及无解)
法2.化为\(a \cdot x + n \cdot k = b\) 扩展欧几里得
线性同余方程组 中国剩余定理 板题测试:51nod1079
题意
\[ \begin{align} a &\equiv a_1 \pmod{p_1} \\ a &\equiv a_2 \pmod{p_2} \\ &\ldots \\ a &\equiv a_k \pmod{p_k} \end{align} \]
给出a数组和p数组(两两互质),求x
题解
可以证明模p有唯一解.\(p = p_1 p_2 \cdots p_k\)
待定系数法设\(x = x_1 + x_2 p_1 + x_3 p_1 p_2 + \ldots + x_k p_1 \ldots p_{k-1}\)
然后带入上面的n个方程。由于解的构造形式,我们可以不用高斯消元解出这个方程组复杂度依然是O(k^2)。
\(a_1 \equiv x_1 \pmod{p_1}.\)
\(a_2 \equiv x_1 + x_2 p_1 \pmod{p_2}.\)
\(\begin{array}{rclr} a_2 - x_1 &\equiv& x_2 p_1 &\pmod{p_2} \\ (a_2 - x_1) r_{12} &\equiv& x_2 &\pmod{p_2} \\ x_2 &\equiv& (a_2 - x_1) r_{12} &\pmod{p_2} \end{array}\)
其中\(r_{ij} = (p_i)^{-1} \pmod{p_j}\)
\(x_3 \equiv ((a_3 - x_1) r_{13} - x_2) r_{23} \pmod{p_3}.\)
代码
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
//#define int long long
const int N = 0, mod = 0;
const int maxn = 15;
typedef long long ll;
int p[maxn], a[maxn], x[maxn], P[maxn];
int n;
/*模数为质数时:
ll kpow(ll x, ll y, ll mod) {
ll ret = 1;
while (y) {
if (y & 1)ret = ret * x%mod;
x = x * x%mod;
y >>= 1;
}
return ret;
}
int inv(int x, int y) {
return kpow(x, y - 2, y);
}*/
int gcd(int a, int b, int & x, int & y) {
if (a == 0) {
x = 0;
y = 1;
return b;
}
int x1, y1;
int d = gcd(b % a, a, x1, y1);
x = y1 - (b / a) * x1;
y = x1;
return d;
}
int inv(int x, int y) {
int ret, tmp;
gcd(x, y, ret, tmp);
return ret;
}
int CRT() {
int ret = 0;
rep(i, 1, n) {
x[i] = a[i];
rep(j, 1, i - 1) {
x[i] = inv(p[j], p[i]) * (x[i] - x[j]);
x[i] = x[i] % p[i];
if (x[i] < 0)
x[i] += p[i];
}
}
rep(i, 1, n) {
ret += x[i] * P[i - 1];
}
return ret;
}
int main() {
cin >> n;
P[0] = 1;
rep(i, 1, n) {
cin >> p[i] >> a[i];
P[i] = P[i - 1] * p[i];
}
cout << CRT() << endl;
cin >> n;
}
/*
3
2 1
3 2
5 3
*/
\(n!{\%p}\)
题解
列出来分块\(\begin{eqnarray} n!_{\%p}&=& \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 1}_{1\text{st}} \cdot \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 2}_{2\text{nd}} \cdot \ldots \\ & & \cdot \underbrace{1 \cdot 2 \cdot 3 \cdot \ldots \cdot (p-2) \cdot (p-1) \cdot 1}_{p\text{th}} \cdot \ldots \cdot \quad \underbrace{1 \cdot 2 \cdot \cdot \ldots \cdot (n \bmod p)}_{\text{tail}} \pmod{p}. \end{eqnarray}\)
用威尔逊定理\((p-1)! \bmod p = p-1\)
每一块留下最后一个元素组成新串
代码
int factmod(int n, int p) {
int res = 1;
while (n > 1) {
res = (res * ((n/p) % 2 ? p-1 : 1)) % p;//p-1的奇数次幂为p-1,偶次幂为1
for (int i = 2; i <= n%p; ++i)//最后一块
res = (res * i) % p;
n /= p;//每块最后一个元素组成新串
}
return res % p;
}
原根——模意义下的幂指对
题意
原根是什么啊!?
定义模n意义下的原根g (g is a primitive root modulo n):
定义:每个与n互质的数和g的幂同余(mod n)
感性上理解就是 g可以让 g^k%n 产生所有与n互质的数
从英语理解就是 g is also called the generator of the multiplicative group of integers modulo n
用数学定义g就是\(\forall a:gcd(a,n)=1 \exists k:g^k \equiv a \pmod n\)用数学语言定义就是:设n>1,gcd(g,n)=1,称使得\(g^d \equiv 1 \pmod n\) 的最小正整数d为g对模数n的阶,记为\(\delta _ n(g)\)。 如果\(\delta _ n(g)=\phi(n)\) 则称g是n的原根。
存在性:n=1,2,4,p^k,2p^k.时才有,n为质数时,g^k%n取遍1道n-1.
性质1:\(g^{\phi (n)} \equiv 1 \pmod n\) 且phi(n)是最小的使其同余1的数。
性质2:\(\phi (\phi (n) )\)为n的原根数量
题解
一些定理推导,
\(g^d \not \equiv 1 \pmod n\) 本来要对所有d<phi(n)判一遍,
但是由于拉格朗日定理(orz不是群论,不是高数orz),判\(d \, | \, \phi (n)\)就够了。
为了加速枚举所有约数d,我们质因子分解phi(n),然后对每个质因子pi判一下\(d \, | \, \phi (n)\)即可
\(O(Ans . log \phi (n) . \log n)\)ans是原根的大小。
代码
int powmod (int a, int b, int p) {
int res = 1;
while (b)
if (b & 1)
res = int (res * 1ll * a % p), --b;
else
a = int (a * 1ll * a % p), b >>= 1;
return res;
}
int generator (int p) {
vector<int> fact;
int phi = p-1, n = phi;
for (int i=2; i*i<=n; ++i)
if (n % i == 0) {
fact.push_back (i);
while (n % i == 0)
n /= i;
}
if (n > 1)
fact.push_back (n);
for (int res=2; res<=p; ++res) {
bool ok = true;
for (size_t i=0; i<fact.size() && ok; ++i)
ok &= powmod (res, phi / fact[i], p) != 1;
if (ok) return res;
}
return -1;
}
\(a^x \equiv b \pmod m\)
题意
模意义下取对数,a,m互质
题解
GSBS giant step, baby step.
令x=pn-q, 则称p is giant step,q is baby step.而这个n就是控制步长比例的
原式等价于\(a^x \equiv b \pmod m,\)
\(a^{np} \equiv ba^q \pmod m\) (因为互质?)
等价于求\(f_1(p) = f_2(q).\)这两个整点函数交点\(p \in [1; \lceil \frac{m}{n} \rceil ]\) \(q \in [0; n]\)
有这么一种算法:
第一步:求出f1所有函数值并排序,
第二步:然后用所有的f2函数值再f1中用二分查找查询。
n如何选取?从复杂度来算,
第一步:\(O\left(\left\lceil \frac{m}{n} \right\rceil \left(\log m + \log \left\lceil \frac{m}{n} \right\rceil \right)\right) = O\left( \left\lceil \frac {m}{n} \right\rceil \log m\right)\)
第二步: \(O\left(n \left(\log m + \log \frac{m}{n} \right) \right) = O\left(n \log m\right).\)
在 \(n = \sqrt{m}.\) 时取到最小值\(O(\sqrt {m} \log m).\)
代码
//用unordered_map优化
//用迭代a来省略二次幂
//用map<int, vector<int>> 如果有多组解
int powmod(int a, int b, int m) {
int res = 1;
while (b > 0) {
if (b & 1) {
res = (res * a) % m;
}
a = (a * a) % m;
b >>= 1;
}
return res;
}
int solve(int a, int b, int m) {
int n = (int) sqrt (m + .0) + 1;
map<int, int> vals;
for (int p = n; p >= 1; --p)
vals[powmod(a, p * n, m)] = p;
for (int q = 0; q <= n; ++q) {
int cur = (powmod(a, q, m) * b) % m;
if (vals.count(cur)) {
int ans = vals[cur] * n - q;
return ans;
}
}
return -1;
}
//用hash代替map
//非互质情况也能做
const int MAXN = (int)1e6+7;
const int mod=76543;
struct HaHashsh{
int head[mod],p;
struct ss{int v;int w,last;}G[77000];
void clear(){memset(head,0,sizeof(head));p=0;}
int find(int a){
int A=a%mod;
for(int i=head[A];i;i=G[i].last)
if(G[i].v==a)return G[i].w;
return -1;
}
void insert(int a,int b){
int A=a%mod;
G[++p]=(ss){a,b,head[A]};head[A]=p;
}
}hs;
ll ExBSGS(ll a,ll b,ll MOD) {
a = a%MOD; b = b%MOD;
if (b == 1) return 0;
int cnt = 0;
ll t = 1;
for (ll g = __gcd(a,MOD);g != 1;g = __gcd(a,MOD)) {
if (b%g) return -1; //
MOD /= g,b /= g;
t = t*a/g%MOD;
cnt ++;// x -> x1
if (t == b) return cnt; //b1 = 1
}
hs.clear();
int m = ceil(sqrt(1.0*MOD));
ll e = 1;
for (int i = 0;i < m;i ++) { //a^i i = [0,sqrt(m)) b*a^i
hs.insert(e*b%MOD,i);
e = e*a%MOD;
}
ll nw = t;
for (int i = 1;i <= m+1;i ++) {
nw = e*nw%MOD;
int v = hs.find(nw);
if (v != -1) {
return i*m-v+cnt;
}
}
return -1;
}
\(x^k \equiv a \pmod n\)
题意
模意义下开根,模数n是素数.
题解
用原根把原式转化为求对数:
因为n为素数,所以n的原根g可以生成1~n-1的所有数。
不妨设 \(x=(g^y)\)
\(\therefore\) 原式\(\Longleftrightarrow\) \((g^y)^k \equiv a \pmod n\)
\(\Longleftrightarrow\)\((g^k)^y \equiv a \pmod n\)
这样就变成了求x。
如何找到所有解?
\(x_0 = g^{y_0} \pmod n\) ,而 \(g^\phi (n) = 1\)
\(\therefore\) \(x^k \equiv g^{ y_0 \cdot k + l \cdot \phi (n)} \equiv a \pmod n \forall l \in Z\)(乘以任意个phi(n)).
\(\therefore\) \(x = g^{y_0 + \frac {l \cdot \phi (n)}{k}} \pmod n \forall l \in Z\)
代码
//头文件省略
int gcd(int a, int b) {
return a ? gcd(b % a, a) : b;
}
int powmod(int a, int b, int p) {
int res = 1;
while (b > 0) {
if (b & 1) {
res = res * a % p;
}
a = a * a % p;
b >>= 1;
}
return res;
}
// Finds the primitive root modulo p
int generator(int p) {
vector<int> fact;
int phi = p-1, n = phi;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
fact.push_back(i);
while (n % i == 0)
n /= i;
}
}
if (n > 1)
fact.push_back(n);
for (int res = 2; res <= p; ++res) {
bool ok = true;
for (int factor : fact) {
if (powmod(res, phi / factor, p) == 1) {
ok = false;
break;
}
}
if (ok) return res;
}
return -1;
}
// This program finds all numbers x such that x^k = a (mod n)
int main() {
int n, k, a;
scanf("%d %d %d", &n, &k, &a);
if (a == 0) {
puts("1\n0");
return 0;
}
int g = generator(n);
// Baby-step giant-step discrete logarithm algorithm
int sq = (int) sqrt (n + .0) + 1;
vector<pair<int, int>> dec(sq);
for (int i = 1; i <= sq; ++i)
dec[i-1] = {powmod(g, i * sq * k % (n - 1), n), i};
sort(dec.begin(), dec.end());
int any_ans = -1;
for (int i = 0; i < sq; ++i) {
int my = powmod(g, i * k % (n - 1), n) * a % n;
auto it = lower_bound(dec.begin(), dec.end(), make_pair(my, 0));
if (it != dec.end() && it->first == my) {
any_ans = it->second * sq - i;
break;
}
}
if (any_ans == -1) {
puts("0");
return 0;
}
// Print all possible answers
int delta = (n-1) / gcd(k, n-1);
vector<int> ans;
for (int cur = any_ans % delta; cur < n-1; cur += delta)
ans.push_back(powmod(g, cur, n));
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
for (int answer : ans)
printf("%d ", answer);
}