初等数论(二)
数论分块
数论分块可以快速计算一些含有除法向下取整的和式(即形如 的和式)。当可以在 内计算 或已经预处理出 的前缀和时,数论分块就可以在 的时间内计算上述和式的值。
long long H(int n) {
long long res = 0;
int l = 1, r;
while (l <= n) {
r = n / (n / l); //与n/l相等的最大的r
res += (r - l + 1) * 1LL * (n / l);
l = r + 1;
}
return res;
}莫比乌斯反演
设 为两个数论函数。
形式一:如果有 ,那么有 。
形式二:如果有 ,那么有 。
其中, 为莫比乌斯函数,定义为
莫比乌斯函数不仅是积性函数,还有如下性质:
即 ,可推出
线性筛莫比乌斯函数
int mu[maxn], lp[maxn], p[maxn], tot;
void getmu() {
mu[1] = 1;
for (int i = 2;i < maxn;i++) {
if (!lp[i]) lp[i] = p[++tot] = i, mu[i] = -1;
for (int j = 1;j <= tot && p[j] <= lp[i] && p[j] * i < maxn;++j) {
lp[p[j] * i] = p[j];
if (i % p[j] == 0) {
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = -mu[i];
}
}
}中国剩余定理
中国剩余定理可求解如下形式的一元线性同余方程组(其中 两两互质):
理解:
可以设:
其中, ,则,为使上式满足方程里的所有条件,还可以推出,用扩展欧几里得算法可求得。
void exgcd(int a, int b, int& d, int& x, int& y) {
if (!b) { d = a; x = 1; y = 0; }
else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}
int mul_mod(int a, int b, int p) {
if (b == 0)
return 0;
if (b == 1)
return a % p;
int temp = mul_mod(a, b >> 1, p);
if (b & 1)
return ((temp + temp) % p + a) % p;
return (temp + temp) % p;
}
int china(int a[], int b[], int len)
{
int ans = 0, lcm = 1, d, x, y;
for (int i = 1;i <= len;++i) {
lcm *= b[i];
a[i] = (a[i] % b[i] + b[i]) % b[i];
}
for (int i = 1;i <= len;++i)
{
int tp = lcm / b[i];
exgcd(tp, b[i], d, x, y);
x = (x % b[i] + b[i]) % b[i];
ans = (ans + mul_mod(mul_mod(tp, x, lcm), a[i], lcm)) % lcm;
}
return (ans + lcm) % lcm;
}扩展中国剩余定理
当模数不两两互质时,可用扩展剩余定理求解。
设两个方程分别是 、;
将它们转化为不定方程:,其中 是整数,则有 。
由裴蜀定理,当 不能被 整除时,无解;
其他情况下,可以通过扩展欧几里得算法解出来一组可行解 ;
则原来的两方程组成的模方程组的解为 ,其中 ,。
多组方程时按以上方法进行合并。
#include<iostream>
#include<algorithm>
typedef __int128 ll;
using namespace std;
ll A, B;
void exgcd(ll a, ll b, ll &d, ll& x, ll& y) {
if (!b) { d = a;x = 1; y = 0; }
else { exgcd(b, a % b, d, y, x);y -= x * (a / b); }
}
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
ll lcm(ll a, ll b) {
return a / gcd(a, b) * b;
}
void merge(ll a, ll b) {
ll p, q, d;
exgcd(B, b, d, p, q);
ll c = a - A;
if (c % d) {
puts("-1");
exit(0);
}
p = p * (c / d) % (b / d);
if (p < 0) p += b / d;
ll mod = lcm(b, B);
A = (B * p + A) % mod;
if (A < 0) A += mod;
B = mod;
}
int main() {
int t;
cin >> t;
while (t--) {
long long a, b;
cin >> b >> a;
if (A == 0 && B == 0)
A = a, B = b;
else
merge(a, b);
}
cout << (long long)(A%B) << endl;
return 0;
}扩展BSGS
利用扩展BSGS算法解决离散对数问题:
若互质,则:
令 ,其中 ,则有 ,稍加变换,则有 。
我们已知的是 ,所以我们可以先算出等式右边的 的所有取值,枚举 ,用 hash/map 存下来,然后逐一计算 ,枚举 ,寻找是否有与之相等的 ,从而我们可以得到所有的 ,。
注意到 均小于 ,所以时间复杂度为 ,用 map 则多一个 。
若不互质,则:
当 时,在模 意义下 存在逆元,因此可以使用 BSGS 算法求解。于是我们想办法让他们变得互质。
具体地,设 。如果 ,则原方程无解。否则我们把方程同时除以 ,得到
如果 和 仍不互质就再除,设 。如果 ,则方程无解;否则同时除以 得到
同理,这样不停的判断下去。直到 。
记 ,于是方程就变成了这样:
由于 ,于是推出 。这样 就有逆元了,于是把它丢到方程右边,这就是一个普通的 BSGS 问题了,于是求解 后再加上 就是原方程的解啦。
注意,不排除解小于等于 的情况,所以在消因子之前做一下 枚举,直接验证 ,这样就能避免这种情况。
int exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int BSGS(int a, int b, int p) {
if (1 % p == b % p) return 0;
int k = sqrt(p) + 1;
map<int, int> hs;
for (int y = 0, r = b % p; y < k; y++) {
hs[r] = y;
r = (LL)r * a % p;
}
int ak = 1;
for (int i = 1; i <= k; i++) ak = (LL)ak * a % p;
for (int x = 1, l = ak; x <= k; x++) {
if (hs.count(l)) return k * x - hs[l];
l = (LL)l * ak % p;
}
return -INF;
}
int exBSGS(int a, int b, int p) {
b = (b % p + p) % p;
if (1 % p == b % p) return 0;
int x, y;
int d = exgcd(a, p, x, y);
if (d > 1) {
if (b % d) return -INF;
exgcd(a / d, p / d, x, y);
return exBSGS(a, (LL)b / d * x % (p / d), p / d) + 1;
}
return BSGS(a, b, p);
}