老久以前的了,以前忘放上面了(差点丢了/jk)
欧拉筛素数
(O(n))筛法
for(int i = 2; i <= n; i++) {
if(vis[i] == 0) pre[tot++] = i;//存下每个素数
for(int j = 0; j <= tot, i * pre[j] <= n; j++){
vis[pre[j] * i] = 1;
if (i % pre[j] == 0){//保证每个数只筛一次
break;
}
}
}
保证每个数被它的最小质因子筛掉
为什么 (i % pre[j] == 0) 就 break ??
证明
(pre) 数组中的素数是递增的
当 (i) 能整除 (pre_j),那么 (i * pre_{j+1}) 这个合数肯定被 (pre_j) 乘以某个数筛掉
因为 (i) 中含有 (pre_j) , (pre_j) 比 (pre_{j+1}) 小,即 (i = k * pre_j)
那么 (i * pre_{j+1} = (k * pre_j) * pre_{j + 1})
设 (pre_{j + 1} * k = k') 所以 (i * pre_{j +1} = k'*pre_j)
同余
在模意义下,加减乘都不会影响答案
加减很显然
证明乘??
目标:((x*y) ~mod~ p~= (x ~mod~ p) * (y ~mod~ p))
(x = a_1*p + b_1,y = a_2 * p + b_2)
(x*y = (a_1*a_2 * p + a_1 * b_2 + b_1*a_2)*p + b_1*b_2)
很显然 (x*y) 对 (p) 取模就是 ((b_1*b_2)) 对 (p) 取模
栗题
我们把最后组合起来的数拆成每一位相加的形式,因为是在模9的意义下,所以 (10^n~mod~ 9 = 1) 所以最后答案也就是把每一位相加都取模
辗转相除法证明
(gcd(a, b) = gcd(b, a~mod~b))
设 (g|a),(g|b), (r = a - k * b = a~mod~b)
可得 (frac{r}{g}= frac{a}{g} - frac{k*b}{g})
看得出,右边显然是个整数,所以 (g | r)
exgcd
可解方程
求 (x,y)
设 (a' = b, b' = a~mod~ b)
(a'x + b'y = gcd(a', b'))
根据 (gcd) 得 (gcd(a', b') = gcd(a, b))
(a'x + b'y = gcd(a, b))
(bx + (a - b * lfloorfrac{a}{b} floor)*y = gcd(a, b))
(ay + b(x - lfloor frac{a}{b} floor*y) = gcd(a,b))
( herefore x = y, y = x - lfloor frac{a}{b} floor*y)
一个回溯的过程
应用(扩欧解不定方程)
解形如 (ax+by=c) 的不定方程
先根据 (exgcd) 求出 (ax + by = gcd(a, b)) 的一组解 (x_0, y_0)
根据裴蜀定理,当 (c ~mod~ gcd(a, b) = 0) 的时候,此方程有解
求解
设 (d = gcd(a , b))
方程两边同时乘以 (frac{c}{d}) , 得到 (a*frac{c}{d}x_0 + b*frac{c}{d}y_0 = c)
然后就有了一组解 (x_1 = frac{c}{d}x_0, x_2 = frac{c}{d}y_0)
求最小整数解(使 (x_1) 最小化)
现在已经由 (exgcd) 求得任意一组 (x, y) 了
为了满足等式成立
那么 (x) 最小正整数解为 ((x + b) \% b)
为什么??
可以这样想:
先在已经求出一组解使得 (ax + by = 1) 也就是 (y = frac{1 - ax}{b})
因为 (y) 是整数,所以此时 (b | 1 - ax) 现在想让 (x) 改变,如果 (x) 改变的值不是 (b) 的倍数,那就会使得 (1 - ax) 不能整除 (b) ,(y) 就不是整数了,所以 (x) 的改变量一定是 (b) 的倍数才可以
code
d = exgcd(a, b, x, y);//返回的是 a,b 的 gcd
if(c % d == 0)
{
x *= c / d;
t = b / d;
t = abs(t);
x = (x % t + t) % t;
printf("%d
", x);
}
栗题
/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define int long long
using namespace std;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int x_1, y_1;
int exgcd(int a, int b, int &x1, int &y1){
if(!b){
x_1 = 1, y_1 = 0;
return a;
}
int d = exgcd(b, a % b, x_1, y_1);
int t = x_1; x_1 = y_1, y_1 = t - a / b * y_1;
return d;
}
int x, y, m, n, l;
signed main(void){
x = read(), y = read(), m = read(), n = read(), l = read();
int a = n - m, b = l, c = x - y;
int d = exgcd(a, b, x_1, y_1);
if(c % d == 0) {
x_1 *= c / d;
int t = b / d;
t = abs(t);
x_1 = (x_1 % t + t) % t;
printf("%lld", x_1);
}
else printf("Impossible");
return 0;
}
中国剩余定理
若 (m_1, m_2, m_3……m_n) 是两两互质的正整数,有同余方程组
[egin{cases} x equiv a_1 (mod~m_1)\ x equiv a_2 (mod~m_2)\ ……\ x equiv a_n (mod~m_n)\ end{cases} ]求 (x) ?
(M = prod_{i=1}^{n} m_i), (M_i = M/m_i),(t_i) 是线性同余方程 (M_it_i equiv 1(mod ~m_i)) 的一个解
则 (x = sum^{n}_{i = 1}a_iM_it_i)
证明:
根据一个定理
两数不能整除,若除数扩大(或缩小)了几倍,而被除数不变,则其商和余数也同时扩大(或缩小)相同的倍数(余数必小于除数)
(证明: 假设 a % b = d,a / b = k, a = kb + d, a - kd = d,a,b 同时扩大 z 倍,得到 z(a - kd) = zd;)
所以可以得出 (a_iM_it_i equiv a_i(mod~~m_i))
显然 $ M_i$ 除了可以 (m_i) 之外都可以整除其他 (m)
(ecause forall k ot= i,a_iM_it_i = 0(mod~m_k))
(ecause a_iM_it_iequiv a_i(~mod~m_i))
所以带入原式得到:(x = sum^{n}_{i = 1}a_iM_it_i)
可以结合 despair_ghost 这篇博客理解,讲的特别好
实现
回到上面的式子
(M_it_i equiv 1(mod ~m_i))
可以得到 (M_it_i - k*m = 1)
(M_i, m) 都是定值,因为 (M_i, m) 互质,所以就可以 (exgcd) 求解了
/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define int long long
using namespace std;
const int N = 20;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int Ans, M = 1;
int n, m[N], a[N];
void exgcd(int a, int b, int &d, int &x, int &y){
if (!b){d = a,x = 1, y = 0;return;}
exgcd(b, a % b, d, x, y);
int t = x; x = y, y = t - (a / b) * y;
}
void Int_China() {
int Mi, x, y, d;
for (int i = 1; i <= n; i++) {
Mi = M / m[i];
exgcd(Mi, m[i], d, x, y);
Ans = ((Ans + x * Mi * a[i]) % M + M) % M;
}
}
signed main(void) {
n = read();
for(int i = 1; i <= n; i++) m[i] = read(), a[i] = read(), M *= m[i];
Int_China();
printf("%lld
",(Ans + M) % M );
return 0;
}
扩展CRT
求 (x) ?
和中国剩余定理类似,就是没有互质的条件了
数学归纳法:
假设已经求出前 (k-1) 个方程组成的同余方程组的一个解为 (x)
且有 (M=prod_{i=1}^{ileq k-1})
则前 (k-1) 个方程的方程组通解为 (x+i*M(iin Z))
那么对于加入第 (k) 个方程后的方程组
我们就是要求一个正整数 (t),使得 (x+t*M equiv a_k(mod~m_k))
转化一下上述式子得 (t*M equiv a_k-x(mod m_k))
对于这个式子我们已经可以通过扩展欧几里得求解t
若该同余式无解,则整个方程组无解, 若有,则前 (k) 个同余式组成的方程组的一个解解为(x_k=x+t*M)
乘法逆元
解决模意义下没法进行除法的问题
已知 (a|b) 求 (frac{b}{a}mod~m)
设法把除法转化到乘法上去
找一个 (a^{-1}) 满足 $a*a^{-1}equiv1(mod~m) $
换个形式
(ax equiv 1(mod~m))
根据扩展中国剩余定理就可以求出来了
可与忽视 p 不为质数的情况:只要 gcd(a,m) 不为 1,那么 a 的逆元就不存在
欧拉函数
欧拉函数 (varphi(m)) 表示不超过 (m) 且和 (m) 互素的正整数的个数
(m = prod p_i^{a_i})
求单值欧拉函数?
公式:
(p_1,p_2…p_k) 为 (n) 的所有质因子
证明
考虑容斥
因为要求与 (n) 互质的数的个数,因为在 (n) 之内,每一个 (p_i) 的倍数分布是均匀的, (n) 之内有 (frac{1}{p_i}) 的数是 (p_i) 的倍数,因此有 (n*(1 - frac{1}{p_i})) 个数不是 (p_i) 的倍数,同理有 (n*(1 - frac{1}{p_j})) 个数不是 (p_j) 的倍数,根据乘法原理,有 (n*(1 - frac{1}{p_i})* (1 - frac{1}{p_j})) 个数既不是 (p_i) 的倍数,也不是 (p_j) 的倍数。进而就可以推广得到有 (n*prod^{k}_{i = 1}(1 - p_i)) 个数与 (n) 互质 (有点生物算遗传的意思)
严谨证明(来自 (attack) 大佬)
1.当 (n=1) 时
很明显,答案为 (1)
2.当 (n) 为质数时
根据素数的定义,答案为 (n−1)
3.当 (n) 为合数时
我们已经知道了 (n) 为素数的情况
对 (n) 进行质因数分解
设 (n=a^{p_{1}^1}∗a^{p_{2}^{2}}...∗a^{p_{k}^k})
假设 (k=1)
那么 (varphi(p^k)=p^k−p^{k−1})
证明:考虑容斥,与一个数互素的数的个数就是这个数减去与它不互素的数的个数
因为 (p) 是素数,所以在 (p^k) 中与其不互素的数为 (1∗p,2∗p....p^{k−1}∗p),有(p^{k−1})个
当 (k eq 1) 时
(varphi(n) = varphi(a_1^{p_{1}^1}∗a_2^{p_{2}^{2}}...∗a_k^{p_{k}^k}))
(prod^k_{i = 1}a_i^{p_i} - a_{i}^{p_{i - 1}})
(prod^k_{i = 1}a_i^{p_i}(1-frac{1}{p_i}))
(n*prod^k_{i = 1}(1-frac{1}{p_i}))
单值求法:若 (gcd(a, b) = 1) 就更新 (phi_n) 的值
求 (1-n) 的 (varphi(i)) ?
埃氏筛
(O(n)) 扫一遍,每扫到一个质数,就更新一下它的所有的倍数
时间复杂度:(O(n*ln ln n))
/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e5 + 4;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int n, phi[N];
int main(void){
n = read();
for (int i = 1; i <= n; i++) phi[i] = i;
for (int i = 2; i <= n; i++){
if(phi[i] == i) {
for (int j = i; j <= n; j += i)
phi[j] = phi[j] / i * (i - 1);
}
}
for(int i = 1; i <= n; i++){
printf("%d
", phi[i]);
}
return 0;
}
康托展开
康托展开是全排列到自然数的一一映射,该自然数可以代表该序列在全排列中的排名(从0开始)
正康托展开:全排列到名次
(a_5 = lbrace2,3,5,1,4 brace)
求该序列的名次
考虑比它小的序列
(1*4!+1*3!+2*2!+0+0 = 34) 排列组合
可以这么理解:后面比当前小的数的个数*后面数的全排列
逆康托展开:名次到全排列
(1*4!+1*3!+2*2!+0+0 = 34)
得到 (a_5 = lbrace2,3,5,1,4 brace)
卢卡斯定理/Lucas 定理
这直接给出公式,证明过程涉及二项式内容,感性理解 = =
(C_n^m = Lucas(n, m, p))
(Lucas(n, m, p) = C(n\%p,m\%p) * Lucas(frac{n}{p }, frac{m}{p}, p))
(Lucas(x, 0, p) = 1, C(a, b) = (frac{a!}{(a - b)!}^{p - 2})~mod~p)
int n, m, p, a[N];//a:阶乘
int qpow(int x, int y) {
int tmp = 1;
while(y) {
if (y & 1) tmp = (tmp * x) % p;
y >>= 1;
x = (x * x) % p;
}
return tmp;
}
int C(int n, int m) {
if (m > n) return 0;
return (a[n] * qpow(a[m], p - 2) % p * qpow(a[n - m], p - 2) % p) % p;
}
int Lucas(int n, int m) {
if (!m) return 1;
return C(n % p, m % p) * Lucas(n / p, m / p) % p;
}
signed main(){
int T = read();
while(T--) {
a[0] = 1;
n = read(), m = read(), p = read();
for (int i = 1; i <= p; i++) a[i] = a[i - 1] * i % p;
printf("%lld
", Lucas(n + m, n));
}
return 0;
}