算法模板(5):数学(2):数论

  • 易错:
  1. 忘记在主程序调用筛法函数,并查集忘记调用初始化函数!
  2. 分解质因数时,如果先筛素数再分解质因数,别忘把divisor函数中的好几个i全改成prime[i],否则错的很惨。

数论

质数

(1)质数的判定

素数计数函数:小于或等于 x x x 的素数个数,用 π ( x ) \pi(x) π(x) 表示。随着 x x x 的增大,有这样近似的结果: π ( x ) ∼ x ln ⁡ x \pi(x) \sim \frac{x}{\ln x} π(x)lnxx

暴力判素数

这个方法好,不用开根号,也不用担心溢出了。复杂度仍是 O ( n ) O(\sqrt n) O(n )

bool is_prime(int x) {
	if (x < 2) return false;
	for (int i = 2; i <= x / i; i++) {
		if (x % i == 0) return false;
	}
	return true;
}

Miller-Rabin 素性测试

输入多个数字,判断有多少个数是质数,输入数字范围是 n < 2 31 n < 2^{31} n<231

对数 n n n 进行 k k k 轮测试的时间复杂度是 O ( k log ⁡ 3 n ) O(k \log^3n) O(klog3n)

因为 1 1 1 p p p 的二次剩余,若 p p p 是奇素数, x 2 ≡ 1 ( m o d    p ) x^2 \equiv 1(\mod p) x21(modp) 必有两根 1 , p − 1 1,p-1 1,p1. 因此 a = q ∗ 2 p a = q*2^p a=q2p,若 a q ≠ 1 a ^{q} \ne 1 aq=1,那么必然先遇到 − 1 -1 1 再遇到 1 1 1 ,否则与 x 2 ≡ 1 ( m o d    p ) x^2 \equiv 1(\mod p) x21(modp)​ 有两根相矛盾.

i64 mod_pow(i64 x, i64 n, i64 mod)
{
    i64 res = 1;
    while(n)
    {
        if(n & 1) res = mul(res, x, mod);
        x = mul(x, x, mod);
        n >>= 1;
    }
    return res;
}


bool millerRabin(i64 n)
{
    if(n == 1) return false;
    if(n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13) return true;
    if(n % 2 == 0 || n % 3 == 0 || n % 5 == 0 || n % 7 == 0 || n % 11 == 0 || n % 13 == 0) return false;
    i64 a = n - 1;
    int b = 0;
    while(a % 2 == 0) a /= 2, b++;

    i64 test[7] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022LL};

    for(int i = 0, j; i < 7 && test[i] < n; i++)
    {
        i64 x = test[i], v = mod_pow(x % mod, a, n);
        if(v == 1) continue;
        for(j = 0; j < b; j++)
        {
            if(v == n - 1) break;
            v = mul(v, v, n);
        }
        if(j >= b) return false;
    }
    return true;
}

(2)分解质因数

暴力分解 O( n \sqrt{n} n )

  • 这个采用白书模板(略作改动)。因为多数时候不是让你输出质因数,而是要保存质因数。
void prime_factor(int x, map<int, int>& res) {
	for (int i = 2; i <= x / i; i++) {
		while (x % i == 0) {
			res[i]++;
			x /= i;
		}
	}
    //分解质因数,这一行千万别少,也别写成 res.empty() !!!
	if (x != 1) res[x]++;
}

可以先筛掉 n \sqrt n n 范围内的素数,然后用深搜分解质因数. 这里提供一个暴力分解求质因子幂次之和的模板

#define x first
#define y second
int get_div(int n, int id)
{
    auto it = mp.find(n);
    if(it != mp.end()) return it->y;
    if(n % prime[id] == 0)
    {
        return mp[n] = get_div(n / prime[id], id) + 1;
    }
    else if(prime[id + 1] * prime[id + 1] <= n) return mp[n] = get_div(n, id + 1);

    return mp[n] = n == 1 ? 0 : 1;
}

先筛素数后分解

st 数组保存最小素因数

  • 筛素数: O ( n ) O(n) O(n)​;分解素因数: O ( log ⁡ n ) O(\log n) O(logn)​.
int prime[maxn], cnt, st[maxn];
void sieve(int N) {
	for (int i = 2; i <= N; i++) {
		if (!st[i]) st[i] = prime[cnt++] = i;
		for (int j = 0; prime[j] <= N / i; j++) {
			st[prime[j] * i] = prime[j];
			if (i % prime[j] == 0) break;
		}
	}
}

vector<int> divisor(int N) {
	vector<int> res;
	for (int i = N; i > 1; ) {
		//找到 j 的最小质因数
		int x = st[i];
		res.push_back(x);
		//把j一直除以最小质因数,直到不能被 x 整除为止。
		while (i % x == 0) {
			i /= x;
		}
	}
	return res;
}

(3)筛素数

埃氏筛

  • 复杂度仅有 O ( n ∗ log ⁡ log ⁡ n ) O(n * \log \log n) O(nloglogn)​,因此对于程序设计竞赛的规模,可以看成线性,因此是线性筛。
  • 素数定理: 1 ∼ n 1\sim n 1n 中,大约有 n / ln ⁡ n n / \ln n n/lnn 个质数。
  • 下面函数返回值是的是 1 ∼ n 1\sim n 1n 中有多少素数。
int prime[maxn];
bool is_prime[maxn + 1];

int sieve(int n) {
	int p = 0;
	for (int i = 0; i <= n; i++) is_prime[i] = true;
	is_prime[0] = is_prime[1] = false;
	for (int i = 2; i <= n; i++) {
		if (is_prime[i]) {
			prime[p++] = i;
			for (int j = 2 * i; j <= n; j += i) is_prime[j] = false;
		}
	}
	return p;
}

线性筛

  • n是1e6时,埃式筛和线性筛效率差不太多。但是,n是1e7时,时间仅是埃式筛的一半。
  • 原理:因为我们是从小到大枚举质数。因此:
  • i   m o d   p j = 0 i\ mod\ p_j = 0 i mod pj=0 时, p j p_j pj 一定是x的最小质因子。
  • i i i​ % p j p_j pj​ != 0 时, p j p_j pj​ 也一定是 p j ∗ i p_j * i pji​ 的最小质因子,且 p j p_j pj​ 一定小于 i i i​ 的最小质因子。
  • 对于一个合数x,假设 p j p_j pj 是x的最小质因子。当 i i i 枚举到 x / p j x/p_j x/pj 的,就会被筛掉。因此,每个数都是按照最小质因子进行筛选,因此只会筛到一次,所以是线性的。
int prime[maxn], cnt;
bool st[maxn];
void get_primes(int N) {
	for (int i = 2; i <= N; i++) {
		if (!st[i]) prime[cnt++] = i;
		for (int j = 0; prime[j] <= N / i; j++) { //枚举到i的最小质因子的时候就停下来。
			st[prime[j] * i] = true;
			if (i % prime[j] == 0) break;  //pj 是 i 的最小质因子。
		}
	}
}

区间筛法

196. 质数距离

  • 找到 [2, b \sqrt b b ​] 间的所有质因子。因此,一个数是合数,必然存在一个 [2, b \sqrt b b ​] 之间且小于自己的质因子。
  • 因此,对于 [2, b \sqrt b b ​​] 的每个质数,将 [L, R] 中所有 p p p 的倍数筛掉(至少两倍)。
  • 因此,复杂度是 O ( n ∗ log ⁡ log ⁡ n ) O(n*\log \log n) O(nloglogn)​。
//prime1保存[2, sqrt(b)]素数, prime2保存[a, b]之间的素数. maxn1是sqrt(b)的最大值, maxn2是b - a的最大值。st1保存保存[2, sqrt(b)]是否是素数, prime2保存[a, b]是否为素数。
int prime1[maxn1], cnt1;
bool st1[maxn1], st2[maxn2];
ll prime2[maxn2], cnt2;
//筛[2, sqrt(b)]素数
void sieve(int N) {
	for (int i = 2; i <= N; i++) {
		if (!st1[i]) prime1[cnt1++] = i;
		for (int j = 0; prime1[j] <= N / i; j++) {
			st1[prime1[j] * i] = true;
			if (i % prime1[j] == 0) break;
		}
	}
}
//筛[a, b]之间的素数.
void segment_sieve(ll a, ll b) {
	memset(st2, 0, sizeof st2);
	cnt2 = 0;
	for (int i = 0; i < cnt1; i++) {
		ll p = prime1[i];
		for (ll j = max(2 * p, (a + p - 1) / p * p); j <= b; j += p) {
			st2[j - a] = true;
		}
	}
	for (int i = 0; i <= b - a; i++) {
		if (!st2[i] && a + i >= 2) prime2[cnt2++] = a + i;
	}
}

(4)威尔逊定理

HDU 2973 YAPTCHA (威尔逊定理及其逆定理)

约数

(1)试除法求约数 O( n \sqrt{n} n )

  • 调用函数时,auto res = divisor(x) 很方便。
  • 是否需要排序看题目要求。实际上,int范围内,约数最多的数也就1600个左右,排序时间其实可以忽略不计。
vector<int> divisor(int n) {
	vector<int> res;
	for (int i = 1; i <= n / i; i++) {
		if (n % i == 0) {
			res.push_back(i);
			if (i != n / i) res.push_back(n / i);
		}
	}
	sort(res.begin(), res.end());
	return res;
}

(2)约数个数

求一个数的约数个数

  • 如果 N = p 1 c 1 ∗ p 2 c 2 ∗ . . . ∗ p k c k N = p_1^{c_1} * p_2^{c_2} * ... *p_k^{c_k} N=p1c1p2c2...pkck;约数个数: ( c 1 + 1 ) ∗ ( c 2 + 1 ) ∗ . . . ∗ ( c k + 1 ) (c_1 + 1) * (c_2 + 1) * ... * (c_k + 1) (c1+1)(c2+1)...(ck+1)
ll divisor(int x) {
	unordered_map<int, int> res;
	for (int i = 2; i <= x / i; i++) {
		while (x % i == 0) {
			res[i]++;
			x /= i;
		}
	}
	if (x != 1) res[x]++;
	ll ans = 1;
	for (auto p : res) ans = ans * (p.second + 1) % mod;
	return ans;
}

筛法求约数个数

  • 1 ∼ N 1 \sim N 1N 中约数个数之和的数量级应该是 N ∗ log ⁡ N N*\log N NlogN.
  • 一个简易的循环算出每个数的约数个数 O ( n ∗ log ⁡ n ) O(n*\log n) O(nlogn)
int nums[maxn]
for(int i = 1; i <= N; i++){
	for(int j = i; j <= N; j += i){
		nums[j]++;
	}
}

(3)约数之和

  • 如果 N = p 1 c 1 ∗ p 2 c 2 ∗ . . . ∗ p k c k N = p_1^{c_1} * p_2^{c_2} * ... *p_k^{c_k} N=p1c1p2c2...pkck;约数之和: ( p 1 0 + p 1 1 + . . . + p 1 c 1 ) ∗ . . . ∗ ( p k 0 + p k 1 + . . . + p k c k ) (p_1^0 + p_1^1 + ... + p_1^{c_1}) * ... * (p_k^0 + p_k^1 + ... + p_k^{c_k}) (p10+p11+...+p1c1)...(pk0+pk1+...+pkck)
    稍微改一下上面那个哈希表的遍历即可。
ll ans = 1;
for (auto prime : res) {
	int p = prime.first, a = prime.second;
	ll t = 1;
	//小心这里,是循环a次。想想为什么。
	for (int i = 0; i < a; i++) {
		t = (t * p + 1) % mod;
	}
	ans = ans * t % mod;
}

(4)最大公约数

  • 最大公约数:gcd
  • 最小公倍数:LCM;最近公共祖先:LCA;最长公共子序列:LCS
int gcd(int a, int b) {
	if (b == 0) return a;
	return gcd(b, a % b);
}

线段上网格点的个数

  • 题意:给定平面上两个坐标均为整数 P 1 = ( x 1 , y 1 ) , P 2 = ( x 2 , y 2 ) P_1=(x_1,y_1),P_2=(x_2,y_2) P1=(x1,y1),P2=(x2,y2),线段 P 1 P 2 P_1P_2 P1P2上,除了 P 1 P_1 P1 P 2 P_2 P2上一共有几个坐标均为整数的点。
  • ∣ x 1 − x 2 ∣ = 0 |x_1-x_2| = 0 x1x2=0 ∣ y 1 − y 2 ∣ = 0 |y_1 - y_2|=0 y1y2=0时,答案是0。否则答案是 g c d ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) − 1 gcd(|x_1-x_2|, |y_1 - y_2|) - 1 gcd(x1x2,y1y2)1
  • 其实就是看看扩展欧几里得可以得到几组解的问题。

(5)分解因数

  • 给出一个正整数a,要求分解成若干个正整数的乘积,即 a = a 1 ∗ a 2 ∗ a 3 ∗ . . . ∗ a n a = a_1 * a_2 * a_3 * ... * a_n a=a1a2a3...an,并且 1 < a 1 < = a 2 < = a 3 < = . . . < = a n 1 < a_1 <= a_2 <= a_3 <= ... <= a_n 1<a1<=a2<=a3<=...<=an,问这样的分解的种数有多少。注意到a = a也是一种分解。
  • 有一个很简洁的dfs方法,可以积累一下。
#include 
using namespace std;
int f(int n, int m){
    int ans = 1;//算上本身那种情况   
    if (n == 1) return 0;
    for (int i = m; i <= n / i; i++){
        //从2开始遍历找所有的能分解的情况        
        if (n % i == 0){
            //上面相当于把子问题漏掉的那种情况加上了           
            ans += f(n / i, i);
            //把子问题的所有情况也加上  
            //因为 a = a1 * a2 * a3 * ... * an,并且1 < a1 <= a2 <= a3 <= ... <= an,        
            //因为后面的因数要比前面大,漏了这一个        
        }
    }
    return ans;
}
int main(){
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++){
        int a;
        cin >> a;
        int ans = f(a, 2);
        cout << ans << endl;

    }
    return 0;
}

Pollard-Rho 算法

i64 Pollard_Rho(i64 x)
{
    //找到 x 的一个因子
    i64 s = 0, t = 0;
    i64 c = (i64)(rnd_64()) % (x - 1) + 1;
    int step = 0, goal = 1;
    i64 val = 1;
    for (goal = 1;; goal *= 2, s = t, val = 1)
    {
        //倍增优化
        for (step = 1; step <= goal; ++step)
        {
            t = ((i128)t * t + c) % x;
            val = (i128)val * abs(t - s) % x;
            if ((step % 127) == 0)
            {
                i64 d = __gcd(val, x);
                if (d > 1) return d;
            }
        }
        i64 d = __gcd(val, x);
        if (d > 1) return d;
    }
}

(6)整除分块

  • 计算 ∑ i = 1 a ⌊ a / i ⌋ \sum\limits_{i=1}^{a}\lfloor a/i\rfloor i=1aa/i,设 g ( x ) g(x) g(x) ⌊ a / x ⌋ \lfloor a / x\rfloor a/x相等的几个数中,x最大值。即 ⌊ a / x ⌋ = ⌊ a / g ( x ) ⌋ \lfloor a/x\rfloor = \lfloor a/ g(x)\rfloor a/x=a/g(x)⌋ ⌊ a / x ⌋ > ⌊ a / ( g ( x ) + 1 ) ⌋ \lfloor a / x \rfloor>\lfloor a / (g(x)+1)\rfloor a/x>a/(g(x)+1)⌋。而 g ( x ) = ⌊ a ⌊ a x ⌋ ⌋ g(x) =\lfloor \frac{a}{\lfloor\frac{a}{x}\rfloor}\rfloor g(x)=xaa复杂度就是 O ( n ) O(\sqrt n) O(n )
  • 上取整转下取整: ⌈ m n ⌉ = ⌊ m − 1 n ⌋ + 1 \lceil \frac{m}{n} \rceil = \lfloor \frac{m-1}{n} \rfloor + 1 nm=nm1+1.

199. 余数之和

  • 题意:给出正整数n和k,计算 k   m o d   1 + k   m o d   2 + k   m o d   3 + … + k   m o d   n k\ mod\ 1 + k\ mod\ 2 + k\ mod\ 3 + … + k\ mod\ n k mod 1+k mod 2+k mod 3++k mod n的值。
  • 化简:原式 = ∑ i = 1 N ( K − ⌊ K i ⌋ ∗ i ) = N ∗ K − ∑ i = 1 N ⌊ K i ⌋ ∗ i =\sum\limits_{i=1}^{N}(K-\lfloor \frac{K}{i}\rfloor*i)=N*K-\sum\limits_{i=1}^{N}\lfloor \frac{K}{i}\rfloor*i =i=1N(KiKi)=NKi=1NiKi
#include
#include
using namespace std;
typedef long long ll;
ll N, K;
int main() {
	scanf("%lld%lld", &N, &K);
	ll ans = N * K;
	for (ll l = 1, r; l <= N; l = r + 1) {
		if (K / l == 0) break;
		r = min(K / (K / l), N);
		ans -= (K / l) * (l + r) * (r - l + 1) / 2;
	}
	printf("%lld\n", ans);
}

费马小定理与欧拉定理

费马小定理

p p p 为素数, g c d ( a , p ) = 1 gcd(a, p) = 1 gcd(a,p)=1,则 a p − 1 ≡ 1 ( m o d    p ) a^{p-1} \equiv 1(\mod p) ap11(modp).

  • 若整数b,m互质,并且对于任意的整数 a,如果满足 b ∣ a b\mid a ba,则存在一个整数x,使得 a/b ≡ a∗x(mod m),则称x为b的模m乘法逆元,记为 b − 1 b^{−1} b1​(mod m)。
  • b 存在乘法逆元的充要条件是b与模数m互质。当模数m为质数时, b m − 2 b^{m−2} bm2即为b的乘法逆元。当求出 b mod m为0时,即b是m的倍数,求逆元无解。否则就有解。
#include
typedef long long ll;
ll mod_pow(ll x, ll n, ll mod) {
	ll res = 1;
	while (n) {
		if (n & 1) res = res * x % mod;
		x = x * x % mod;
		n >>= 1;
	}
	return res;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		ll a, p;
		scanf("%lld%lld", &a, &p);
		ll ans = mod_pow(a, p - 2, p);
		if (a % p) printf("%lld\n", ans);
		else printf("impossible\n");
	}
	return 0;
}

欧拉函数

(1)定义法 ( O ( n ) ) (O(\sqrt n)) (O(n ))

  • 欧拉函数: 1 ~ N 中与 N 互质的数的个数被称为欧拉函数,记为ϕ(N)。
  • 若在算数基本定理中, N = p 1 a 1 ∗ p 2 a 2 ∗ … ∗ p m a m N=p_1^{a_1}*p_2^{a_2}*…*p_m^{a_m} N=p1a1p2a2pmam,则:
    φ ( N ) = N ∗ p 1 − 1 p 1 ∗ p 2 − 1 p 2 ∗ … ∗ p m − 1 p m \varphi(N)=N*\frac{p_1 - 1}{p_1}∗\frac{p_2−1}{p_2}∗…∗\frac{p_m−1}{p_m} φ(N)=Np1p11p2p21pmpm1
int N;
void solve() {
	int ans = N;
	for (int i = 2; i <= N / i; i++) {
		if (N % i == 0) {
			ans = ans / i * (i - 1);
			while (N % i == 0) N /= i;
		}
	}
	if (N > 1) ans = ans / N * (N - 1);
	printf("%d\n", ans);
}

(2)筛法求欧拉函数

  • 当i时质数时, φ ( i ) = i − 1 \varphi(i) = i-1 φ(i)=i1。当 i m o d    p j = 0 i \mod p_j = 0 imodpj=0 时, φ ( i ∗ p j ) = φ ( i ) ∗ p j \varphi(i * p_j) = \varphi(i)*p_j φ(ipj)=φ(i)pj。当 i m o d    p j ≠ 0 i \mod p_j \ne 0 imodpj=0 时, φ ( i ∗ p j ) = φ ( i ) ∗ ( p j − 1 ) \varphi(i * p_j) = \varphi(i)*(p_j-1) φ(ipj)=φ(i)(pj1)
  • 不要忘记将phi[1]初始化为1。
int prime[maxn], phi[maxn], cnt;
bool st[maxn];
void get_eulers(int N) {
	phi[1] = 1;
	for (int i = 2; i <= N; i++) {
		if (!st[i]) {
			prime[cnt++] = i;
			phi[i] = i - 1;
		}
		for (int j = 0; prime[j] <= N / i; j++) {
			st[i * prime[j]] = true;
			if (i % prime[j] == 0) {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = phi[i] * (prime[j] - 1);
		}
	}
}

扩展欧拉定理

$$
a^b \equiv \begin{cases}a^{b\mod \varphi§}, & \gcd(a,p) = 1\
a^b, & \gcd(a, p) \ne 1, b < \varphi§ \
a^{b \mod \varphi§ + \varphi§}, & \gcd(a, p) \ne 1, b \ge \varphi§

\end{cases}
$$

不定方程

扩展欧几里得算法

  • 给两个整数 a , b a, b a,b a a a b b b 不一定互质),求满足 a x + b y = g c d ( a , b ) ax + by = gcd(a, b) ax+by=gcd(a,b) 的整数对 ( x , y ) (x,y) (x,y)​。
  • 注意,这里的 ( x , y ) (x, y) (x,y) ​是不唯一的。这个方法只是求了一个解。若求出一组 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)​,通解应该是 x = ( x 0 + b g c d ( a , b ) ∗ k ) , y = ( y 0 − a g c d ( a , b ) ∗ k ) x=(x_0+\frac{b}{gcd(a, b)} *k), y=(y_0-\frac{a}{gcd(a, b)}*k) x=(x0+gcd(a,b)bk),y=(y0gcd(a,b)ak)​。
  • 这个 e x g c d exgcd exgcd 函数的返回值就是 ( a , b ) (a, b) (a,b)
#include
int exgcd(int a, int b, int& x, int& y) {
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int a, b, x, y;
		scanf("%d%d", &a, &b);
		exgcd(a, b, x, y);
		printf("%d %d\n", x, y);
	}
	return 0;
}

线性同余方程

  • 一次同余方程 a x ≡ b ( m o d   m ) ax\equiv b(mod\ m) axb(mod m)​​ 有解,则 ( a , m ) ∣ b (a, m)|b (a,m)b​​
  • 给定 n n n​ 组数据 a i , b i , m i a_i,b_i,m_i ai,bi,mi​,对于每组数求出一个 x i x_i xi​,使其满足 a i ∗ x i ≡ b i ( m o d   m i ) a_i∗x_i≡b_i(mod\ m_i) aixibi(mod mi)​​,如果无解则输出impossible。
#include
typedef long long ll;
int exgcd(int a, int b, int& x, int& y) {
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int a, b, m, x, y;
		scanf("%d%d%d", &a, &b, &m);
		int d = exgcd(a, m, x, y);
		if (b % d) printf("impossible\n");
		else printf("%lld\n", (ll)x * (b / d) % m);
	}
	return 0;
}

扩展欧几里得求逆元

#include
using namespace std;
typedef long long ll;
ll exgcd(ll a, ll b, ll& x, ll& y)
{
    if(!b){
        x = 1, y = 0;
        return a;
    }
    ll d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        //求 a 在模 b 意义下的逆元
        ll a, b, x, y;
        scanf("%lld%lld", &a, &b);
        exgcd(a, b, x, y);
        x = (x % b + b) % b;
        if(x) printf("%lld\n", x);
        else printf("impossible\n");
    }
    return 0;
}

中国剩余定理

  • 对于 ∀ i ∈ [ 1 , n ] , x ≡ a i ( m o d   m i ) ∀i∈[1,n],x≡a_i(mod\ m_i) i[1,n],xai(mod mi)。令 M = m 1 ∗ m 2 ∗ . . . ∗ m n M =m_1*m_2*...*m_n M=m1m2...mn M i = M m i M_i=\frac{M}{m_i} Mi=miM,则解为 x = a 1 ∗ M 1 ∗ M 1 − 1 + a 2 ∗ M 2 ∗ M 2 − 1 + . . . + a n ∗ M n ∗ M n − 1 x=a_1*M_1*M_1^{-1}+a_2*M_2*M_2^{-1}+...+a_n*M_n*M_n^{-1} x=a1M1M11+a2M2M21+...+anMnMn1 。其中 M i ∗ M i − 1 ≡ 1 ( m o d   m i ) M_i*M_{i}^{-1}\equiv 1(mod\ m_i) MiMi11(mod mi)
  • 中国剩余定理要求 m 1 , m 2 , . . . , m n m_1, m_2, ...,m_n m1,m2,...,mn​ 两两互质。这里的求逆元用扩展欧几里得最快。

204. 扩展中国剩余定理

看清m和a,和上面的定理不太照应。而且公式一定要看清,超级超级容易弄混。

  • 给定 2 n 2n 2n​​​​ 个整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an​​​​ 和 m 1 , m 2 , … , m n m_1,m_2,…,m_n m1,m2,,mn​​​​, 求一个最小的非负整数 x,满足 ∀ i ∈ [ 1 , n ] , x ≡ m i ( m o d    a i ) ∀i∈[1,n],x≡m_i(\mod a_i) i[1,n],xmi(modai)​​​​。
  • 这道题的 m i m_i mi​ 并非两两互质。因此不可以用中国剩余定理。不过可以从原理出发去解。

设两个方程分别是 x ≡ m 1 ( m o d a 1 ) x\equiv m_1 \pmod {a_1} xm1(moda1)​、 x ≡ m 2 ( m o d a 2 ) x\equiv m_2 \pmod {a_2} xm2(moda2)​;

将它们转化为不定方程: x = a 1 p + m 1 = a 2 q + m 2 x=a_1p+m_1=a_2q+m_2 x=a1p+m1=a2q+m2​,其中 p , q p, q p,q​ 是整数,则有 a 1 p − a 2 q = m 2 − m 1 a_1p-a_2q=m_2-m_1 a1pa2q=m2m1​。

由裴蜀定理,当 m 2 − m 1 m_2-m_1 m2m1​​ 不能被 gcd ⁡ ( a 1 , a 2 ) \gcd(a_1,a_2) gcd(a1,a2)​​ 整除时,无解;

其他情况下,可以通过扩展欧几里得算法解出来一组可行解 ( p , q ) (p, q) (p,q)

则原来的两方程组成的模方程组的解为 x ≡ M ( m o d A ) x\equiv M\pmod A xM(modA)​​,其中 M = a 1 p + m 1 M=a_1p+m_1 M=a1p+m1​​, A = lcm ( a 1 , a 2 ) A=\text{lcm}(a_1, a_2) A=lcm(a1,a2)​​。

#include
#include
using namespace std;
typedef long long ll;
ll exgcd(ll a, ll b, ll& x, ll& y) {
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}
int main() {
	int N;
	scanf("%d", &N);
	ll a1, m1;
	bool ok = true;
	scanf("%lld%lld", &a1, &m1);
	for (int i = 1; i < N; i++) {
		ll a2, m2, k1, k2;
		scanf("%lld%lld", &a2, &m2);
		//k1 * a1 - k2 * a2 = m2 - m1, 求出 k1 和 k2 的值。
		ll d = exgcd(a1, a2, k1, k2);
		if ((m2 - m1) % d) {
			ok = false;
			break;
		}
		//k1 = k0 + a2 / d * k,将k1扩大m2 - m1倍,再想办法让k1是最小正整数。
		k1 *= (m2 - m1) / d;
		ll t = a2 / d;
		k1 = (k1 % t + t) % t;  //这里可以将k1变成最小正整数。
		//x = k0 * a1 + m1 + [a1, a2];
		m1 = a1 * k1 + m1;
		a1 = abs(a1 / d * a2);
	}
    
	if (ok) printf("%lld\n", (m1 % a1 + a1) % a1);
	else printf("-1\n");
	return 0;
}

数论函数

定义在 N ∗ \N^* N 上的函数 f f f: N ∗ → A \N^* \rightarrow A NA​ 都可以称作是数论函数,其中 A A A 可以是由加减乘运算的集合.

一些常见的数论函数

  • 单位函数 ε ( n ) = { 1 , n = 1 0 , o t h e r w i s e \varepsilon(n) = \begin{cases}1,n=1 \\ 0,otherwise\end{cases} ε(n)={1,n=10,otherwise

  • 幂函数 I d k ( n ) = n k . Id_k(n) = n^k. Idk(n)=nk. k = 1 k=1 k=1 时为恒等函数 I d ( n ) Id(n) Id(n),当 k = 0 k = 0 k=0 时为常数函数 1 ( n ) 1(n) 1(n)

  • 除数函数 σ k ( n ) = ∑ d ∣ n d k \sigma_k(n) = \sum_{d \mid n}d^k σk(n)=dndk​​,当 k = 1 k=1 k=1 时为因数和函数 σ ( n ) \sigma(n) σ(n),当 k = 0 k=0 k=0 时为因数个数函数 σ 0 ( n ) \sigma_0(n) σ0(n). 也写作 τ ( n ) = ∑ d ∣ n 1 \tau (n) = \sum_{d \mid n}1 τ(n)=dn1,表示正整数 n n n 的正因子个数.

数论函数 f f f 叫做是积性函数,如果对于任意两个互素的正整数 n n n m m m​ ,都满足 f ( n m ) = f ( n ) f ( m ) f(nm) = f(n)f(m) f(nm)=f(n)f(m).

n = p 1 a 1 p 2 a 2 . . . p k a k n=p_1^{a_1}p_2^{a_2}...p_k^{a_k} n=p1a1p2a2...pkak,则有
f ( n ) = f ( p 1 a 1 ) f ( p 2 a 2 ) . . . f ( p k a k ) = ∏ i = 1 k f ( p i a i ) . f(n) = f(p_1^{a_1})f(p_2^{a_2})...f(p_k^{a_k}) = \prod\limits_{i=1}^{k}f(p_i^{a_i}). f(n)=f(p1a1)f(p2a2)...f(pkak)=i=1kf(piai).

狄利克雷卷积:对于数论函数 f f f g g g,它们的卷积表示成 f ∗ g f*g fg​,卷积的结果是一个数论函数 h h h​,且
( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) g ( n d ) = ∑ a b = n f ( a ) g ( b ) . (f*g)(n) = \sum\limits_{d \mid n}f(d)g(\frac{n}{d}) = \sum\limits_{ab=n}f(a)g(b). (fg)(n)=dnf(d)g(dn)=ab=nf(a)g(b).

f , g f,g f,g 都是积性函数,那么 f ∗ g f*g fg 也是积性函数

证明
( f ∗ g ) ( a ) ⋅ ( f ∗ g ) ( b ) = ( f ∗ g ) ( a b ) . (f*g)(a) \cdot (f*g)(b) = (f*g)(ab). (fg)(a)(fg)(b)=(fg)(ab).
狄利克雷卷积的一些性质 证明

交换律: f ∗ g = g ∗ f f*g=g*f fg=gf

结合律: ( f ∗ g ) ∗ h = f ∗ ( g ∗ h ) (f * g) * h=f * (g * h) (fg)h=f(gh)

分配律: ( f + g ) ∗ h = f ∗ h + g ∗ h (f + g) * h = f * h + g * h (f+g)h=fh+gh.

等式的性质: f = g f=g f=g 的充要条件是 f ∗ h = g ∗ h f*h = g*h fh=gh,其中数论函数 h ( x ) h(x) h(x) 要满足 h ( 1 ) ≠ 0 h(1) \ne 0 h(1)=0.

单位元:即单位函数 ε ( n ) = { 1 , n = 1 0 , o t h e r w i s e \varepsilon(n) = \begin{cases}1,n=1 \\ 0,otherwise\end{cases} ε(n)={1,n=10,otherwise

假设 f ∗ g = ε f*g=\varepsilon fg=ε,则称 g g g f f f​ 的狄利克雷逆元,记作 f − 1 f^{-1} f1. f f f 有逆元的必要条件是 f ( 1 ) ≠ 0 f(1) \ne 0 f(1)=0.

f − 1 ( n ) = { 1 f ( n ) , n = 1 − 1 f ( 1 ) ∑ d ∣ 1 , d > 1 f ( d ) f − 1 ( n d ) f^{-1}(n) = \begin{cases} \frac{1}{f(n)},n=1 \\ -\frac{1}{f(1)} \sum\limits_{d\mid 1, d>1}f(d)f^{-1}(\frac{n}{d}) \end{cases} f1(n)= f(n)1,n=1f(1)1d1,d>1f(d)f1(dn)

实际上,从抽象代数角度看,取狄利克雷卷积为乘法,普通函数加法为加法,则数论函数集构成一个整环。注意它不构成一个,因为并不是每个非零元素(这里的零元是 0 ( x ) : = 0 0(x):=0 0(x):=0​)都有逆元,而必须要满足 f ( 1 ) ≠ 0 f(1) \ne 0 f(1)=0.

需要指出,积性函数必然存在逆元(因为 f ( 1 ) = 1 ≠ 0 f(1) = 1 \ne 0 f(1)=1=0 ),且逆元仍是积性函数。

函数之间的关系 证明

除数函数和幂函数: ( I d k ∗ 1 ) ( n ) = ∑ d ∣ n I d k ( d ) = ∑ d ∣ n d k (Id_k*1)(n) = \sum\limits_{d \mid n}Id_k(d) = \sum\limits_{d \mid n}d^k (Idk1)(n)=dnIdk(d)=dndk,即 I d k ∗ 1 = σ k Id_k * 1 = \sigma_k Idk1=σk

欧拉函数与恒等函数: ( φ ∗ 1 ) ( n ) = n (\varphi * 1)(n) = n (φ1)(n)=n​,该定理可以看作 n n n 的所有因数的欧拉函数和等于 n n n.

莫比乌斯函数和单位函数: ( μ ∗ 1 ) ( n ) = ∑ d ∣ n μ ( d ) = ε ( n ) = [ n = 1 ] (\mu * 1)(n) = \sum\limits_{d|n} \mu(d) = \varepsilon(n) = [n=1] (μ1)(n)=dnμ(d)=ε(n)=[n=1].​​

莫比乌斯函数

莫比乌斯函数就是看质因数分解后,质因子的个数是奇数还是偶数。

x = p 1 α 1 ∗ p 2 α 2 . . . p k α k x = p_1^{\alpha_1}*p_2^{\alpha_2}...p_k^{\alpha_k} x=p1α1p2α2...pkαk,则莫比乌斯函数定义为:
μ ( x ) = { 0 ,   ∃ α i > 1 1 ,   k 为偶数 − 1 ,   k 为奇数 \mu(x)=\begin{cases}0,\ \exist \alpha_i>1\\1,\ k为偶数\\-1,\ k为奇数\end{cases} μ(x)= 0, αi>11, k为偶数1, k为奇数
莫比乌斯函数有如下性质:
∑ d ∣ n μ ( d ) = { 1 , n = 1 0 , n ≠ 1 \sum\limits_{d \mid n} \mu(d) = \begin{cases}1, n=1 \\ 0, n\ne 1 \end{cases} dnμ(d)={1,n=10,n=1
易证:设 n = p 1 α 1 ∗ p 2 α 2 . . . p k α k n = p_1^{\alpha_1}*p_2^{\alpha_2}...p_k^{\alpha_k} n=p1α1p2α2...pkαk​,则 ∑ d ∣ n μ ( d ) = ( 1 − 1 ) k \sum\limits_{d \mid n} \mu(d) = (1-1)^k dnμ(d)=(11)k

又有 [ g c d ( i , j ) = 1 ] = ∑ d ∣ g c d ( i , j ) μ ( d ) [gcd(i,j) = 1] = \sum\limits_{d \mid gcd(i,j)} \mu(d) [gcd(i,j)=1]=dgcd(i,j)μ(d)​.

215. 破译密码

  • 对于给定的整数 a , b a,b a,b​​​ 和 d d d​​​,有多少正整数对 ( x , y ) (x,y) (x,y)​​,满足 1 ≤ x ≤ a , 1 ≤ y ≤ b 1 \le x\le a, 1 \le y\le b 1xa,1yb​​,并且 g c d ( x , y ) = d gcd(x,y)=d gcd(x,y)=d​​​。共 5 ∗ 1 0 4 5*10^4 5104 组询问, a , b , d ≤ 5 ∗ 1 0 4 a,b,d \le 5 * 10^4 a,b,d5104.​
  • 先这样处理:设 a ′ = ⌊ a / d ⌋ , b ′ = ⌊ b / d ⌋ a'=\lfloor a/d\rfloor, b'=\lfloor b/d \rfloor a=a/d,b=b/d​​,那么就是找 a ′ , b ′ a',b' a,b​​ 中有多少对互质的数。可以这样求:合法对数= 总对数-不合法对数。那么,用容斥原理,总对数是 a ′ ∗ b ′ a'*b' ab​​,都有因子 2 2 2​ 的数的个数分别为 a ′ / 2 , b ′ / 2 a'/2,b'/2 a/2,b/2​,记 S 2 = ( a ′ / 2 ) ∗ ( b / 2 ) S_2 = (a'/2) * (b/2) S2=(a/2)(b/2)​。则答案为 S 1 − S 2 − S 3 . . . + S 6 + S 10 . . . S_1-S_2-S_3...+S_6+S_{10}... S1S2S3...+S6+S10...​​
  • 对于这道题,设 a ′ = ⌊ a / d ⌋ , b ′ = ⌊ b / d ⌋ a'=\lfloor a/d\rfloor, b'=\lfloor b/d \rfloor a=a/d,b=b/d​,则答案是 ∑ i = 1 m i n ( a ′ , b ′ ) ⌊ a ′ / i ⌋ ⌊ b ′ / i ⌋ ∗ m u ( i ) \sum\limits_{i=1}^{min(a',b')}\lfloor a'/i\rfloor\lfloor b'/i \rfloor * mu(i) i=1min(a,b)a/ib/imu(i)​.
  • 整道题的代码:
#include
#include
#include
using namespace std;
const int maxn = 50010;
typedef long long ll;
int prime[maxn], cnt;
bool st[maxn];
int mobius[maxn], sum[maxn];   //莫比乌斯函数以及它的前缀和.
//线性筛法求莫比乌斯函数。
void sieve(int N) {
	mobius[1] = 1;
	for (int i = 2; i <= N; i++) {
		if (!st[i]) {
			prime[cnt++] = i;
			mobius[i] = -1;
		}
		for (int j = 0; prime[j] <= N / i; j++) {
			int t = prime[j] * i;
			st[t] = true;
			if (i % prime[j] == 0) {
				mobius[t] = 0;
				break;
			}
			mobius[t] = -mobius[i];
		}
	}
	for (int i = 1; i <= N; i++) sum[i] = sum[i - 1] + mobius[i];
}
int main() {
	sieve(maxn - 1);
	int a, b, d, T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d%d", &a, &b, &d);
		a /= d, b /= d;
		int n = min(a, b);
		ll ans = 0;
		//敲黑板!整除分块。
		for (int l = 1, r; l <= n; l = r + 1) {
			r = min(n, min(a / (a / l), b / (b / l)));
			ans += (ll)(sum[r] - sum[l - 1]) * (ll)(a / l) * (b / l);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

莫比乌斯反演

  • 莫比乌斯函数 x = p 1 α 1 p 2 α 2 . . . p k α k x = p_1^{\alpha_1}p_2^{\alpha_2}...p_k^{\alpha_k} x=p1α1p2α2...pkαk

μ ( x ) = { 0 , ∃ α i ≥ 2. ( − 1 ) k , ∀ α i = 1. \mu(x)=\begin{cases}0,\exist\alpha_i \ge 2. \\ (-1)^k,\forall\alpha_i=1. \end{cases} μ(x)={0,αi2.(1)k,αi=1.

  • 性质

ε ( n ) = ∑ d ∣ n μ ( d ) = { 1 , n = 1 0 , n > 1 . \varepsilon(n) = \sum\limits_{d|n}\mu(d) = \begin{cases}1,n=1\\0, n>1 \end{cases}. ε(n)=dnμ(d)={1,n=10,n>1.

  • 莫比乌斯反演

f ( n ) , g ( n ) f(n),g(n) f(n),g(n)​ 为两个数论函数

如果有 f ( n ) = ∑ d ∣ n g ( d ) f(n)=\sum_{d\mid n}g(d) f(n)=dng(d),那么有 g ( n ) = ∑ d ∣ n μ ( d ) f ( n d ) g(n)=\sum_{d\mid n}\mu(d)f(\frac{n}{d}) g(n)=dnμ(d)f(dn)

如果有 f ( n ) = ∑ n ∣ d g ( d ) f(n)=\sum_{n|d}g(d) f(n)=ndg(d),那么有 g ( n ) = ∑ n ∣ d μ ( d n ) f ( d ) g(n)=\sum_{n|d}\mu(\frac{d}{n})f(d) g(n)=ndμ(nd)f(d)

形式一的证明
∑ d ∣ n μ ( d ) f ( n d ) = ∑ d ∣ n μ ( d ) ∑ k ∣ n d g ( k ) = ∑ k ∣ n g ( k ) ∑ d ∣ n k μ ( d ) = g ( n ) \sum_{d\mid n}\mu(d)f(\frac{n}{d})=\sum_{d\mid n}\mu(d)\sum_{k\mid \frac{n}{d}}g(k)=\sum_{k\mid n}g(k)\sum_{d\mid \frac{n}{k}}\mu(d)=g(n) dnμ(d)f(dn)=dnμ(d)kdng(k)=kng(k)dknμ(d)=g(n)

形式二的证明

∑ n ∣ d μ ( d n ) f ( d ) = ∑ k = 1 + ∞ μ ( k ) f ( k n ) = ∑ k = 1 + ∞ μ ( k ) ∑ k n ∣ d g ( d ) = ∑ n ∣ d g ( d ) ∑ k ∣ d n μ ( k ) = ∑ n ∣ d g ( d ) ϵ ( d n ) = g ( n ) \begin{aligned} &\sum_{n|d}{\mu(\frac{d}{n})f(d)} \\ =& \sum_{k=1}^{+\infty}{\mu(k)f(kn)} = \sum_{k=1}^{+\infty}{\mu(k)\sum_{kn|d}{g(d)}} \\ =& \sum_{n|d}{g(d)\sum_{k|\frac{d}{n}}{\mu(k)}} = \sum_{n|d}{g(d)\epsilon(\frac{d}{n})} \\ =& g(n) \end{aligned} ===ndμ(nd)f(d)k=1+μ(k)f(kn)=k=1+μ(k)kndg(d)ndg(d)kndμ(k)=ndg(d)ϵ(nd)g(n)
思考1:

∑ i = 1 n ∑ j = 1 m [ ( i , j ) = 1 ] \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[(i,j)=1] i=1nj=1m[(i,j)=1]​​.
∑ i = 1 n ∑ j = 1 m [ ( i , j ) = 1 ] = ∑ i = 1 n ∑ j = 1 m ∑ p ∣ ( i , j ) μ ( p ) = ∑ p = 1 n μ ( p ) ∑ p ∣ i ∑ p ∣ j [ p ∣ ( i , j ) ] = ∑ p = 1 n μ ( p ) ∑ p ∣ i [ p ∣ i ] ∑ p ∣ j [ p ∣ j ] ( i ′ = i / p ,   j ′ = j / p ,   n ′ = n / p ,   m ′ = m / p ) = ∑ p = 1 n μ ( p ) ∗ ( ∑ i ′ = 1 n ′ 1 ) ∗ ( ∑ j ′ = 1 m ′ 1 ) = ∑ p = 1 n μ ( p ) ∗ n ′ ∗ m ′ \begin{align} &\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[(i,j)=1]\\ =&\sum\limits_{i=1}^n\sum\limits_{j = 1}^m\sum\limits_{p\mid (i,j)}\mu(p)\\ =& \sum\limits_{p=1}^n \mu(p) \sum\limits_{p \mid i} \sum\limits_{p\mid j} [p \mid (i,j)] \\ =& \sum\limits_{p=1}^n \mu(p) \sum\limits_{p \mid i} [p \mid i] \sum\limits_{p\mid j} [p \mid j] \\ &(i' = i/p,\ j'=j/p,\ n' = n / p,\ m' = m / p)\\ =& \sum\limits_{p=1}^{n}\mu(p) *(\sum\limits_{i'=1}^{n'}1) * (\sum\limits_{j'=1}^{m'}1) \\ =&\sum\limits_{p=1}^n \mu(p)* n'* m' \end{align} =====i=1nj=1m[(i,j)=1]i=1nj=1mp(i,j)μ(p)p=1nμ(p)pipj[p(i,j)]p=1nμ(p)pi[pi]pj[pj](i=i/p, j=j/p, n=n/p, m=m/p)p=1nμ(p)(i=1n1)(j=1m1)p=1nμ(p)nm

思考2:

GCD SUM

给出两个正整数 n n n​ 和 m m m​,求 ∑ i = 1 n ∑ j = 1 m ( i , j ) \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(i,j) i=1nj=1m(i,j)
∑ i = 1 n ∑ j = 1 m ( i , j ) = ∑ p = 1 n ∑ i = 1 n ∑ j = 1 m [ ( i , j ) = p ] ∗ p = ∑ p = 1 n p ∑ i ′ = 1 n / p ∑ j ′ = 1 m / p [ ( i , j ) = 1 ] \begin{align} &\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}(i,j) \\ =& \sum\limits_{p=1}^n \sum\limits_{i=1}^{n}\sum\limits_{j=1}^m [(i,j)=p]*p\\ =& \sum\limits_{p=1}^n p \sum\limits_{i'=1}^{n/p}\sum\limits_{j'=1}^{m/p} [(i,j)=1] \end{align} ==i=1nj=1m(i,j)p=1ni=1nj=1m[(i,j)=p]pp=1npi=1n/pj=1m/p[(i,j)=1]

积性函数

  • 积性函数: ∀ ( a , b ) = 1 , f ( a ∗ b ) = f ( a ) ∗ f ( b ) \forall(a, b) = 1, f(a*b) = f(a)*f(b) (a,b)=1,f(ab)=f(a)f(b)
  • 凡是积性函数,都可以用线性筛法去求。
  • 比如:欧拉函数,莫比乌斯函数

221. 龙哥的问题

  • 题意:给定一个整数 N N N,请你求出 ∑ i = 1 N g c d ( i , N ) \sum\limits_{i = 1}^Ngcd(i,N) i=1Ngcd(iN) 的值。

  • 对于每一个 d d d,把它质因数分解后,都可以找到对应的 p α p^{\alpha} pα. 而 ∏ i = 1 k p i α \prod\limits_{i=1}^{k}p_i^{\alpha} i=1kpiα ∏ i = 1 k ( 1 − 1 p i ) \prod\limits_{i=1}^{k}(1-\frac{1}{p_i}) i=1k(1pi1) 一一对应。因此就写成了上面那种形式。即 ∑ d ∣ n ∏ i = 1 k ( 1 − 1 p i ) = ∏ i = 1 k ( 1 + α i ( 1 − 1 p i ) ) \sum\limits_{d|n}\prod\limits_{i=1}^{k}(1-\frac{1}{p_i}) = \prod\limits_{i=1}^{k}(1+\alpha_i(1-\frac{1}{p_i})) dni=1k(1pi1)=i=1k(1+αi(1pi1))

#include
#include
#include
#include
using namespace std;
typedef long long ll;
int main() {
	ll N;
	cin >> N;
	ll res = N;
	for (ll i = 2; i <= N / i; i++) {
		if (N % i == 0) {
			ll a = 0, p = i;
			while (N % p == 0) a++, N /= p;
			res = res * (p + a * p - a) / p;
		}
	}
	if (N > 1) res = res * (N + N - 1) / N;
	cout << res << endl;
	return 0;
}

二次剩余

基础知识

一个数 a a a,如果不是 p p p 的倍数且模 p p p 同余于某个数的平方,则称 a a a 为模 p p p二次剩余。而一个不是 p p p 的倍数的数 b b b,不同余于任何数的平方,则称 b b b 为模 p p p非二次剩余

对二次剩余求解,也就是对常数 a a a 解下面的这个方程:

x 2 ≡ a ( m o d p ) x^2 \equiv a \pmod p x2a(modp)

通俗一些,可以认为是求模意义下的开方。这里只讨论 p \boldsymbol{p} p 为奇素数 的求解方法,将会使用 Cipolla 算法。

解的数量

对于 x 2 ≡ n ( m o d p ) x^2 \equiv n \pmod p x2n(modp),能满足" n n n 是模 p p p 的二次剩余"的 n n n 一共有 p − 1 2 \frac{p-1}{2} 2p1 个(0 不包括在内),非二次剩余有 p − 1 2 \frac{p-1}{2} 2p1 个。

勒让德符号

( n p ) = { 1 ,   p ∤ n 且 n 是 p 的二次剩余 − 1 ,   p ∤ n 且 n 不是 p 的二次剩余 0 ,   p ∣ n \left(\frac{n}{p}\right)=\begin{cases} 1,\,&p\nmid n \text{且}n\text{是}p\text{的二次剩余}\\ -1,\,&p\nmid n \text{且}n\text{不是}p\text{的二次剩余}\\ 0,\,&p\mid n \end{cases} (pn)= 1,1,0,pnnp的二次剩余pnn不是p的二次剩余pn

欧拉判别准则

( n p ) ≡ n p − 1 2 ( m o d p ) \left(\frac{n}{p}\right)\equiv n^{\frac{p-1}{2}}\pmod p (pn)n2p1(modp)

n n n 是二次剩余,当且仅当 n p − 1 2 ≡ 1 ( m o d p ) n^{\frac{p-1}{2}}\equiv 1\pmod p n2p11(modp)

n n n 是非二次剩余,当且仅当 n p − 1 2 ≡ − 1 ( m o d p ) n^{\frac{p-1}{2}}\equiv -1\pmod p n2p11(modp)​。

Cipolla 算法

找到一个数 a a a 满足 a 2 − n a^2-n a2n非二次剩余,这里通过生成随机数再检验的方法来实现,由于非二次剩余的数量为 p − 1 2 \frac{p-1}{2} 2p1,接近 p 2 \frac{p}{2} 2p​​,所以期望约 2 2 2 次就可以找到这个数。

扩域,这里定义 i 2 = a 2 − n i^2=a^2-n i2=a2n,于是就可以将所有的数表达为 A + B i A+Bi A+Bi 的形式,这里的 A A A B B B 都是模意义下的数,类似复数中的实部和虚部。

在有了 i i i a a a 后可以直接得到答案, x 2 ≡ n ( m o d p ) x^2\equiv n\pmod p x2n(modp) 的解为 ( a + i ) p + 1 2 (a+i)^{\frac{p+1}{2}} (a+i)2p+1

O ( log ⁡ p ) O(\log p) O(logp)

二次剩余模板题

给出 N , p N, p N,p​,求解方程

x 2 ≡ N (   m o d   p ) x^2 \equiv N(\bmod p) x2N(modp)

多组数据。

保证 p 是奇素数。 1 ≤ T ≤ 10000 1≤T≤10000 1T10000, 1 ≤ N , p ≤ 1 0 9 1\le N, p\leq 10^9 1N,p109

#include
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> num;
mt19937 rnd(0);
ll n, p, w;

num mul(num& a, num& b, ll p)
{
    num res;
    res.x = ((a.x * b.x % p + a.y * b.y % p * w % p) + p) % p;
    res.y = ((a.x * b.y % p + a.y * b.x % p) % p + p) % p;
    return res;
}

ll pow_real(ll x, ll n, ll p)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % p;
        x = x * x % p;
        n >>= 1;
    }
    return res;
}

ll pow_imag(num x, ll n, ll p)
{
    num res = {1, 0};
    while(n)
    {
        if(n & 1) res = mul(res, x, p);
        x = mul(x, x, p);
        n >>= 1;
    }
    return res.x;
}

ll cipolla(ll n, ll p)
{
    n %= p;
    if(n == 0) return 0;
    if(p == 2) return n;
    if(pow_real(n, (p - 1) / 2, p) == p - 1) return -1;

    ll a;
    while(1)
    {
        a = rnd() % p;
        w = ((a * a % p - n) % p + p) % p;
        if(pow_real(w, (p - 1) / 2, p) == p - 1) break;
    }
    num x = {a, 1};
    return pow_imag(x, (p + 1) / 2, p);
}

原根

基础知识

:由欧拉定理可知,对 a ∈ Z a\in \mathbb{Z} aZ m ∈ N ∗ m\in\mathbb{N}^{*} mN,若 gcd ⁡ ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1,则 a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1\pmod m aφ(m)1(modm)

因此满足同余式 a n ≡ 1 ( m o d m ) a^n \equiv 1 \pmod m an1(modm) 的最小正整数 n n n 存在,这个 n n n 称作 a a a m m m 的阶,记作 δ m ( a ) \delta_m(a) δm(a)

性质 1 1 1 a , a 2 , ⋯   , a δ m ( a ) a,a^2,\cdots,a^{\delta_m(a)} a,a2,,aδm(a) m m m 两两不同余。

性质 2 2 2:若 a n ≡ 1 ( m o d m ) a^n \equiv 1 \pmod m an1(modm),则 δ m ( a ) ∣ n \delta_m(a)\mid n δm(a)n

性质 3 3 3:设 m ∈ N ∗ m\in\mathbb{N}^{*} mN a , b ∈ Z a,b\in\mathbb{Z} a,bZ gcd ⁡ ( a , m ) = gcd ⁡ ( b , m ) = 1 \gcd(a,m)=\gcd(b,m)=1 gcd(a,m)=gcd(b,m)=1,则
δ m ( a b ) = δ m ( a ) δ m ( b ) \delta_m(ab)=\delta_m(a)\delta_m(b) δm(ab)=δm(a)δm(b)
的充分必要条件是
gcd ⁡ ( δ m ( a ) , δ m ( b ) ) = 1 \gcd\big(\delta_m(a),\delta_m(b)\big)=1 gcd(δm(a),δm(b))=1

性质4:设 k ∈ N k \in \mathbb{N} kN m ∈ N ∗ m\in \mathbb{N}^{*} mN a ∈ Z a\in\mathbb{Z} aZ gcd ⁡ ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1​,则
δ m ( a k ) = δ m ( a ) gcd ⁡ ( δ m ( a ) , k ) \delta_m(a^k)=\dfrac{\delta_m(a)}{\gcd\big(\delta_m(a),k\big)} δm(ak)=gcd(δm(a),k)δm(a)

原根:设 m ∈ N ∗ m \in \mathbb{N}^{*} mN a ∈ Z a\in \mathbb{Z} aZ。若 gcd ⁡ ( a , m ) = 1 \gcd(a,m)=1 gcd(a,m)=1,且 δ m ( a ) = φ ( m ) \delta_m(a)=\varphi(m) δm(a)=φ(m),则称 a a a 为模 m m m 的原根。

原根判定定理:若一个数 g g g 是模 m m m 的原根,则有对于 φ ( m ) \varphi(m) φ(m) 任何大于 1 1 1 且不为自身的因数 p p p,都有 g φ ( m ) / p ≢ 1 ( m o d m ) g^{\varphi(m)/p}\not\equiv 1\pmod m gφ(m)/p1(modm)​。实际上,只需要判断所有

原根个数:若一个数 m m m 有原根,则它原根的个数为 φ ( φ ( m ) ) \varphi(\varphi(m)) φ(φ(m))​​​。

证明:
m m m 有原根 g g g,则:
δ m ( g k ) = δ m ( g ) gcd ⁡ ( δ m ( g ) , k ) = φ ( m ) gcd ⁡ ( φ ( m ) , k ) \delta_m(g^k)=\dfrac{\delta_m(g)}{\gcd\big(\delta_m(g),k\big)}=\dfrac{\varphi(m)}{\gcd\big(\varphi(m),k\big)} δm(gk)=gcd(δm(g),k)δm(g)=gcd(φ(m),k)φ(m)
所以若 gcd ⁡ ( k , φ ( m ) ) = 1 \gcd\big(k,\varphi(m)\big)=1 gcd(k,φ(m))=1​,则有: δ m ( g k ) = φ ( m ) \delta_m(g^k)=\varphi(m) δm(gk)=φ(m)​,即 g k g^k gk​ 也是模 m m m​ 的原根。
而满足 gcd ⁡ ( φ ( m ) , k ) = 1 \gcd\big(\varphi(m),k\big)=1 gcd(φ(m),k)=1​ 且 1 ≤ k ≤ φ ( m ) 1\leq k \leq \varphi(m) 1kφ(m)​ 的 k k k​ 有 φ ( φ ( m ) ) \varphi(\varphi(m)) φ(φ(m))​ 个。所以原根就有 φ ( φ ( m ) ) \varphi(\varphi(m)) φ(φ(m))​ 个。

原根存在定理:一个数 m m m 存在原根当且仅当 m = 2 , 4 , p α , 2 p α m=2,4,p^{\alpha},2p^{\alpha} m=2,4,pα,2pα,其中 p p p 为奇素数, α ∈ N ∗ \alpha\in \mathbb{N}^{*} αN​。

原根模板题

给定整数 n n n,求它的所有原根。

为了减小你的输出量,给出输出参数 d d d,设 n n n 的所有原根有 c c c 个,从小到大分别为 g 1 , … , g c g_1,\ldots,g_c g1,,gc,你只需要依次输出 g d , g 2 d , … , g ⌊ c d ⌋ × d g_d,g_{2d},\ldots,g_{\lfloor\frac{c}{d}\rfloor\times d} gd,g2d,,gdc×d

暴力找最小原根: O ( n 0.25 ∗ log ⁡ 2 φ ( n ) ) O\big(n^{0.25} * \log^2 \varphi(n)\big) O(n0.25log2φ(n))​​​;

n n n 的最小原根,设为 g g g,则 n n n 的所有原根可以由 g g g 的若干次乘方得到。具体地,若 n n n 存在原根,则其原根个数为 φ ( φ ( n ) ) \varphi(\varphi(n)) φ(φ(n)),每一个原根都形如 g k g^k gk 的形式,要求满足 gcd ⁡ ( k , φ ( n ) ) = 1 \gcd(k,\varphi(n))=1 gcd(k,φ(n))=1​。把所有原根排个序输出即可.

#include
using namespace std;
typedef long long ll;
const int N = 1000010;
int prime[N], phi[N], cnt;
int st[N];

void init(int n)
{
    phi[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!st[i]) st[i] = prime[cnt++] = i, phi[i] = i - 1;
        for(int j = 0; prime[j] <= n / i; j++)
        {
            st[i * prime[j]] = prime[j];

            if(i % prime[j] == 0)
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            phi[i * prime[j]] = phi[i] * (prime[j] - 1);
        }
    }
}
vector<int> divisor(int n)
{
    vector<int> res;
    
    while(n > 1)
    {
        int t = st[n];
        res.push_back(t);
        while(n % t == 0) n /= t;
    }
    return res;
}

ll mod_pow(ll x, ll n, ll mod)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}
vector<int> ans;

void solve(ll n)
{
    if(n == 2){
        ans.push_back(1);
        return;
    }
    ll g = -1;
    vector<int> div = divisor(phi[n]);

    for(int i = 1; i <= n; i++)
    {

        if(mod_pow(i, phi[n], n) != 1) continue;
        bool flag = true;
        for(auto p : div)
        {
            if(mod_pow(i, phi[n] / p, n) == 1)
            {
                flag = false;
                break;
            }
        }
        if(flag)
        {
            g = i;
            break;
        }
    }

    if(g == -1) return;
    if(g == 1) ans.push_back(1);
    else
    {
        ll tmp = g;
        for(int i = 1; i <= phi[n]; i++, tmp = tmp * g % n)
        {
            if(__gcd(i, phi[n]) == 1) ans.push_back(tmp);
        }
    }
}

int main()
{
    init(N - 1);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        ans.clear();
        int n, d;
        scanf("%d%d", &n, &d);
        solve(n);
        printf("%d\n", (int)ans.size());
        sort(ans.begin(), ans.end());
        for(int i = d - 1; i < ans.size(); i += d)
        {
            printf("%d ", ans[i]);
        }
        printf("\n");
    }
    return 0;
}

BSGS

  • baby-step giant-step
  • 复杂度: O ( p ) O(\sqrt p) O(p ).

3124. BSGS

给定正整数 a , p , b a,p,b a,p,b,数据保证 a a a p p p 互质。求满足 a x ≡ b ( m o d   p ) a^x\equiv b(mod\ p) axb(mod p) 的最小非负整数 x x x

x = A ⌈ p ⌉ − B x = A \left \lceil \sqrt p \right \rceil - B x=Ap B,其中 0 ≤ A , B ≤ ⌈ p ⌉ 0\le A,B \le \left \lceil \sqrt p \right \rceil 0A,Bp ,则有 a A ⌈ p ⌉ − B ≡ b ( m o d p ) a^{A\left \lceil \sqrt p \right \rceil -B} \equiv b \pmod p aAp Bb(modp),稍加变换,则有 a A ⌈ p ⌉ ≡ b a B ( m o d p ) a^{A\left \lceil \sqrt p \right \rceil} \equiv ba^B \pmod p aAp baB(modp)

我们已知的是 a , b a,b a,b,所以我们可以先算出等式右边的 b a B ba^B baB 的所有取值,枚举 B B B,用 hash/map 存下来,然后逐一计算 a A ⌈ p ⌉ a^{A\left \lceil \sqrt p \right \rceil} aAp ,枚举 A A A,寻找是否有与之相等的 b a B ba^B baB,从而我们可以得到所有的 x x x x = A ⌈ p ⌉ − B x=A \left \lceil \sqrt p \right \rceil - B x=Ap B

注意到 A , B A,B A,B 均小于 ⌈ p ⌉ \left \lceil \sqrt p \right \rceil p ,所以时间复杂度为 Θ ( p ) \Theta\left (\sqrt p\right ) Θ(p ),用 map 则多一个 log ⁡ \log log

代码:

#include
using namespace std;

typedef long long ll;
ll a, b, p;
ll bsgs() {
	if (1 % p == b % p) return 0;
	ll k = sqrt(p) + 1;
	unordered_map<ll, ll> hash;
	for (ll i = 0, j = b % p; i < k; i++) {
		hash[j] = i;
		j = j * a % p;
	}
	ll ak = 1;
	for (int i = 0; i < k; i++) ak = ak * a % p;

	for (ll i = 1, j = ak; i <= k; i++) {
		if (hash.count(j)) return k * i - hash[j];
		j = j * ak % p;
	}
	return -1;
}
int main() {
	while (cin >> a >> p >> b, a || p || b) {
		ll res = bsgs();
		if (res == -1) cout << "No Solution" << endl;
		else cout << res << endl;
	}
	return 0;
}

3125. 扩展BSGS

  • 给定整数 a , p , b a,p,b a,p,b。求满足 a x ≡ b ( m o d   p ) a^x≡b(mod\ p) axb(mod p) 的最小非负整数 x。
  • 注意,这与上一题的区别是, a a a p p p 不一定互质。

代码(这道题卡常数,所以开了O2优化):

#pragma GCC optimize(2)
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;

const int INF = 1e8;

ll exgcd(ll a, ll b, ll& x, ll& y) {
	if (!b) {
		x = 1, y = 0;
		return a;
	}
	ll d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}

ll bsgs(ll a, ll b, ll p) {
	if (1 % p == b % p) return 0;
	ll k = sqrt(p) + 1;
	unordered_map<ll, ll> hash;
	for (ll i = 0, j = b % p; i < k; i++) {
		hash[j] = i;
		j = j * a % p;
	}
	ll ak = 1;
	for (int i = 0; i < k; i++) ak = ak * a % p;
	for (ll i = 1, j = ak; i <= k; i++) {
		if (hash.count(j)) return k * i - hash[j];
		j = j * ak % p;
	}
	return -INF;
}

ll exbsgs(ll a, ll b, ll p) {
	b = (b % p + p) % p;  //防止b是负数
	if (1 % p == b % p) return 0;
	ll x, y;
	ll d = exgcd(a, p, x, y);   //求最大公约数
	if (d > 1) {
		if (b % d) return -INF;   //无解
		exgcd(a / d, p / d, x, y);
		return exbsgs(a, b / d * x % (p / d), p / d) + 1;
	}
	return bsgs(a, b, p);
}
int main() {
	ll a, b, p;
	while (cin >> a >> p >> b, a || p || b) {
		ll res = exbsgs(a, b, p);
		if (res < 0) cout << "No Solution" << endl;
		else cout << res << endl;
	}
	return 0;
}

离散对数

设模 m m m 有原根 g g g,则 { 1 , g , g 2 , . . . , g φ ( m ) − 1 } \{1,g,g^2,...,g^{\varphi(m)-1}\} {1,g,g2,...,gφ(m)1} 为模 m m m​​ 的缩系. 所以对于每个与 m m m 互素的整数 a a a,必存在唯一的整数 k k k,使得
a ≡ g k ( m o d m ) , 0 ≤ k ≤ φ ( m ) − 1 a \equiv g^k \pmod m, 0 \le k \le \varphi(m) - 1 agk(modm),0kφ(m)1
上述的 k k k 叫做 a a a (对于原根 g g g) 模 m m m 的指数,表示成 k = i n d g a k = ind_ga k=indga. 指数也叫做离散对数.

Discrete Roots

给出三个正整数 p , k , a p,k,a p,k,a​,其中 p p p​ 是素数,保证有解,输出所有满足 x k ≡ a ( m o d p ) x^k \equiv a \pmod p xka(modp)​ 且 0 ≤ x ≤ p − 1 0\le x \le p-1 0xp1​​​ 的整数 x x x​​.

2 ≤ p ≤ 1 0 9 , 2 ≤ k ≤ 1 0 5 , 0 ≤ a < p 2 \le p \le 10^9, 2 \le k \le 10^5, 0 \le a < p 2p109,2k105,0a<p​.

方法一

我们令 x = g c x=g^c x=gc g g g p p p 的原根(我们一定可以找到这个 g g g c c c),问题转化为求解 ( g c ) a ≡ b ( m o d p ) (g^c)^a \equiv b \pmod p (gc)ab(modp)。稍加变换,得到

( g a ) c ≡ b ( m o d p ) (g^a)^c \equiv b \pmod p (ga)cb(modp)

于是就转换成了我们熟知的 BSGS 的基本模型了,可以在 O ( p ) O(\sqrt p) O(p ) 解出 c c c,这样可以得到原方程的一个特解 x 0 ≡ g c ( m o d p ) x_0\equiv g^c\pmod p x0gc(modp)

方法二

我们仍令 x = g c x=g^c x=gc,并且设 b = g t b=g^t b=gt,于是我们得到

g a c ≡ g t ( m o d p ) g^{ac}\equiv g^t\pmod p gacgt(modp)

方程两边同时取离散对数得到

a c ≡ t ( m o d φ ( p ) ) ac\equiv t\pmod{\varphi(p)} act(modφ(p))

我们可以通过 BSGS 求解 g t ≡ b ( m o d p ) g^t\equiv b\pmod p gtb(modp) 得到 t t t,于是这就转化成了一个线性同余方程的问题。这样也可以解出 c c c,求出 x x x 的一个特解 x 0 ≡ g c ( m o d p ) x_0\equiv g^c\pmod p x0gc(modp)

你可能感兴趣的:(算法模板,算法,数据结构,排序算法)