数论
基础
-
整除: d 整除 n,记为 d ∣ n d | n d∣n
-
最大公约数:记为 g c d ( a , b ) gcd(a,b) gcd(a,b),有时简记为 ( a , b ) (a,b) (a,b).
- g c d ( 0 , 0 ) gcd(0,0) gcd(0,0)不存在
- g c d ( 0 , a ) = a gcd(0,a)=a gcd(0,a)=a
-
最小公倍数:记为 l c m ( a , b ) lcm(a,b) lcm(a,b)
gcd与lcm的关系
l c m ( a , b ) = a b g c d ( a , b ) lcm(a,b) = \frac {ab}{gcd(a,b)} lcm(a,b)=gcd(a,b)ab
注意:为了避免溢出,应该用 a / g c d ( a , b ) ∗ b a/gcd(a,b)*b a/gcd(a,b)∗b,而不是 a ∗ b / g c d ( a , b ) a*b/gcd(a,b) a∗b/gcd(a,b)
gcd计算
辗转相除法(又名欧几里得算法)
int gcd(int a,int b)
{
return b? gcd(b,a%b) : a;
}
证明: g c d ( a , b ) = g c d ( b , a % b ) gcd(a,b)= gcd(b,a \% b) gcd(a,b)=gcd(b,a%b)
复杂度为 O ( log ( m a x ( a , b ) ) ) O(\log(max(a,b))) O(log(max(a,b)))
例子
-
法一(先打质数表,再判断幂次)
#include<bits/stdc++.h> #define M 2001000 #define clr(c) memset(c,0,sizeof(c)) #define end 123804765 #define inf 0x7fffffff using namespace std; inline int read() { int sum=0,b=1;char c=getchar(); while(!isdigit(c)){if(c=='-') b=-1; c=getchar();} while(isdigit(c)){sum=sum*10+c-'0';c=getchar();} return b*sum; } int t,a0,a1,b0,b1,p[50011],sum; int u[50010]; int main() { int i,j,ans,flag; t=read(); u[1]=1; for(i=2;i<=50000;i++) { if(u[i]==0) { p[++sum]=i; } for(j=1;j<=sum;j++) { if(i*p[j]>50000) break; u[i*p[j]]=1; if(i%p[j]==0) break; } }//打表 while(t--) { ans=1;flag=1; a0=read(),a1=read(),b0=read(),b1=read();//gcd(x,a0)=a1,lcm(x,b0)=b1; if(a0<a1&&b0>b1) {printf("0\n");continue;} for(i=1;i<=sum;i++) { int numa0=0,numa1=0,numb0=0,numb1=0; while(a0%p[i]==0) {numa0++;a0/=p[i];} while(a1%p[i]==0) {numa1++;a1/=p[i];} while(b0%p[i]==0) {numb0++;b0/=p[i];} while(b1%p[i]==0) {numb1++;b1/=p[i];} if(numa1>numa0||numb1<numb0){flag=0;break;} int la=0,lb=0,ra=inf,rb=inf,l=0,r=M; la=numa1;rb=numb1; if(numa0>numa1) ra=la=numa1; if(numb0<numb1) rb=lb=numb1; l=max(la,lb),r=min(ra,rb); if(r<l){ flag=0;break;} ans*=(r-l+1); } if(!(a1==1&&a0==1&&b0==1&&b1==1)) { if(a1>a0||b1<b0) flag=0; // if(a1==a0&&a1!=1) ans<<=1; if(b1==b0&&b1!=1) ans<<=1; } if(flag) printf("%d\n",ans); else printf("0\n"); } return 0; }
法二(参照判断素数的最朴素的方法,一个因子 < n <\sqrt{n} <n,则只用枚举到 n \sqrt{n} n,另一因子用 n / i n/i n/i即可表示)
#include<bits/stdc++.h>
#define ll long long
const int M=1e6+10;
using namespace std;
inline ll read()
{
ll sum=0,b=1; char c=getchar();
while(!isdigit(c)){if(c=='-') b=-1; c=getchar();}
while(isdigit(c)){sum=sum*10+c-48; c=getchar();}
return sum*b;
}
ll gcd(ll x,ll y)
{
return y==0?x:gcd(y,x%y);
}
int t;
ll a0,a1,b0,b1;
int main()
{
int i,j;
t=read();
while(t--)
{
a0=read();a1=read();
b0=read();b1=read();
ll ans=0;
for(i=1;i*i<=b1;i++)
{
ll x=i;
if(b1%x!=0) continue;
if(x%a1==0&&gcd(x/a1,a0/a1)==1&&gcd(b1/x,b1/b0)==1) ans++;
if(x*x==b1) continue;
x=b1/x;
if(x%a1==0&&gcd(x/a1,a0/a1)==1&&gcd(b1/x,b1/b0)==1) ans++;
}
printf("%d\n",ans);
}
return 0;
}
ll gcd(ll x,ll y)
{
return (y==0)?x:gcd(y,x%y);
}
ll x,m,a,t;
ll ans;
ll p[M];
int cnt=0;
bool flag[M];
int num[M];
void sieve()
{
int i,j;
for(i=2;i<=M;i++)
{
if(!flag[i])
p[++cnt]=i;
for(j=1;i*p[j]<=M;j++)
{
flag[i*p[j]]=true;
if(i%p[j]==0)
break;
}
}
}
int main()
{
int t=read();
int i,j;
sieve();
while(t--)
{
ans=1;
clr(num);
a=read(),m=read();
ll n=gcd(a,m);
a/=n; m/=n;
ll ans=m;
for(i=1;i<=cnt&&p[i]*p[i]<=m;i++)
{
if(m%p[i]==0)
{
while(m%p[i]==0)
{
m/=p[i];
num[i]++;
}
ans=ans/p[i]*(p[i]-1);
}
}
if(m!=1)
ans=ans/m*(m-1);
printf("%lld\n",ans);
}
return 0;
}
求 g c d ( a , m ) = g c d ( a + x , m ) gcd(a,m)=gcd(a+x,m) gcd(a,m)=gcd(a+x,m)的 x x x个数, $a\leq m, x\leq m, m\leq 10^{10} $
先求 d = g c d ( a , m ) d=gcd(a,m) d=gcd(a,m), g c d ( a d , m d ) = 1 gcd(\frac{a}{d},\frac{m}{d})=1 gcd(da,dm)=1
易知 d ∣ x d|x d∣x,且 g c d ( a + x d , m d ) = 1 gcd(\frac{a+x}{d},\frac{m}{d})=1 gcd(da+x,dm)=1
因此个数为 ϕ ( m d ) \phi(\frac{m}{d}) ϕ(dm)
扩展欧几里得算法
引理(裴蜀定理)
若a,b为整数,则存在整数 x,y 使得 $ ax+by= gcd(a,b) $
逆元
-
设 a 为整数,n为正整数,若整数 b 满足
a b ≡ 1 ( m o d n ) ab ≡ 1 (mod\ n) ab≡1(mod n) 也可表示为 b ≡ a − 1 ( m o d n ) b \equiv a^{-1}(mod\ n) b≡a−1(mod n)
则称 b 为 a 模 n 的逆元。 -
结论
- 当且仅当 g c d ( a , n ) = 1 gcd(a,n)=1 gcd(a,n)=1,逆元存在
- 若 b1 ,b2 为 a 模 n 的逆元,则必有 b 1 ≡ b 2 ( m o d n ) b1 ≡ b2 (mod\ n) b1≡b2(mod n)
由裴蜀定理,可求出 a x + b y = g c d ( a , b ) ax+by= gcd(a,b) ax+by=gcd(a,b)中的 x,y
a x 1 + b y 1 = g c d ( a , b ) ax_1+by_1= gcd(a,b) ax1+by1=gcd(a,b) 和 b x 2 + ( a − a / b ∗ a ) y 2 = g c d ( b , a % b ) bx_2+(a-a/b*a)y_2= gcd(b,a\%b) bx2+(a−a/b∗a)y2=gcd(b,a%b)
展开整理2式 a y 2 + b ( x 2 − a / b ∗ y 2 ) = g c d ( b , a % b ) = a x 1 + b y 1 ay_2+b(x2-a/b*y_2)=gcd(b,a\%b)=ax_1+by_1 ay2+b(x2−a/b∗y2)=gcd(b,a%b)=ax1+by1
得到
{
x
1
=
y
2
,
y
1
=
x
2
−
a
/
b
∗
y
2
\begin{cases} x_1=y_2, \\ y_1=x_2-a/b*y_2 \end{cases}
{x1=y2,y1=x2−a/b∗y2
int exgcd(int a,int b,int &x,int &y)//一定传地址
{
if(b==0)
{
x=1;
y=0;
return a;
}
int gcd=ecgcd(b,a%b,x,y);
int temp=x;
//回溯
x=y;
y=temp-a/b*y;
return gcd;
}
实际上,求出的解是特解
通解为:
{
x
=
x
0
+
b
/
g
c
d
(
a
,
b
)
×
t
y
=
y
0
−
a
/
g
c
d
(
a
,
b
)
×
t
\begin{cases} x=x_0+b/gcd(a,b)\times t\\ y=y_0-a/gcd(a,b)\times t \end{cases}
{x=x0+b/gcd(a,b)×ty=y0−a/gcd(a,b)×t
应用:求逆元
对于 a x ≡ 1 ( m o d p ) ax\equiv 1(mod\ p) ax≡1(mod p) 可使用 a x + p y = 1 ax+py=1 ax+py=1 (注意判断gcd是否为0)
费马小定理与欧拉定理
费马小定理
当p为质数,且 ( a , p ) = 1 (a,p)=1 (a,p)=1, a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1(mod\ p) ap−1≡1(mod p)
注 只有当p为1质数时可以使用
欧拉定理
若 ( a , m ) = 1 (a,m)=1 (a,m)=1,则 a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1(mod\ m) aφ(m)≡1(mod m)
φ ( m ) \varphi(m) φ(m)为欧拉函数,代表小于等于m且与m互质(包括1)的正整数个数
扩展欧拉定理
a b ≡ { a b m o d φ ( m ) gcd ( a , m ) = 1 a b gcd ( a , m ) ≠ 1 , b < φ ( m ) a ( b m o d φ ( m ) ) + φ ( m ) gcd ( a , m ) ≠ 1 , b ≥ φ ( m ) ( m o d m ) a^b\equiv \begin{cases} a^{b\ mod\ \varphi(m)} &\gcd(a,m)=1 \\ a^b &\gcd(a,m)\neq1,\ b< \varphi(m) \\ a^{(b\ mod\ \varphi(m))+\varphi(m)} &\gcd(a,m)\neq1,\ b\geq \varphi(m) \\ \end{cases} \ \ \ \ (mod\ m) ab≡⎩⎪⎨⎪⎧ab mod φ(m)aba(b mod φ(m))+φ(m)gcd(a,m)=1gcd(a,m)=1, b<φ(m)gcd(a,m)=1, b≥φ(m) (mod m)
a b ≡ a m i n ( b , ( b m o d φ ( m ) ) + φ ( m ) ) ( m o d m ) ( 降 幂 公 式 ) a^b\equiv a^{min(b,(b\ mod\ \varphi(m))+\varphi(m))} (mod\ m)(降幂公式) ab≡amin(b,(b mod φ(m))+φ(m))(mod m)(降幂公式)
例子
#include<bits/stdc++.h>
#define ll long long
const int M=1e7+10;
const int N=1e6+10;
using namespace std;
inline ll read()
{
ll sum=0,b=1; char c=getchar();
while(!isdigit(c)){if(c=='-') b=-1; c=getchar();}
while(isdigit(c)){sum=sum*10+c-48; c=getchar();}
return sum*b;
}
ll f[M];
bool vis[M];
ll p[N],cnt;
void sieve()
{
int i,j;
f[1]=1;
for(i=2;i<=M;i++)
{
if(!vis[i])
{
p[++cnt]=i;
f[i]=i-1;
}
for(j=1;j<=cnt&&i*p[j]<M;j++)
{
vis[i*p[j]]=true;
if(i%p[j]==0)
{
f[i*p[j]]=p[j]*f[i];
break;
}
f[i*p[j]]=f[i]*f[p[j]];
}
}
}
ll q_pow(ll x,ll y,ll mod)
{
ll res=1;
while(y)
{
if(y&1)
{
res=res*x%mod;
}
x=x*x%mod;
y>>=1;
}
return res%mod;
}
ll MOD(ll mod)
{
if(mod==1)
return 0;
return q_pow(2,MOD(f[mod])+f[mod],mod);
}
int main()
{
int i,j;
int t=read();
sieve();
while(t--)
{
ll mod=read();
ll ans=MOD(mod);
printf("%lld\n",ans);
}
return 0;
}
线性求逆元
应用 设 p 为质数,n 为小于 p 的正整数,求 [1,n] 中的每个整数模 p 的逆元。
若对每个数分别求逆元,复杂度为
O
(
n
l
o
g
p
)
O(nlogp)
O(nlogp)。
p很大时使用且为质数(即 > n >n >n)
为求 i i i 在 m o d p modp modp下的逆元
p = k ∗ i + r p=k*i+r p=k∗i+r ( k = ⌊ p i ⌋ , r < i ) (k=\lfloor \frac{p}{i}\rfloor,r<i) (k=⌊ip⌋,r<i)
k ∗ i + r ≡ 0 ( m o d p ) k*i+r\equiv0\ (mod\ p) k∗i+r≡0 (mod p)
k ∗ i ≡ − r ( m o d p ) k*i\equiv -r\ (mod\ p) k∗i≡−r (mod p)
k ∗ r − 1 ≡ − i − 1 ( m o d p ) k*r^{-1}\equiv -i^{-1}\ (mod\ p) k∗r−1≡−i−1 (mod p)
i − 1 ≡ − k ∗ r − 1 ( m o d p ) i^{-1}\equiv -k*r^{-1}\ (mod\ p) i−1≡−k∗r−1 (mod p)
即 i − 1 ≡ − p / i ∗ ( p % i ) − 1 ( m o d p ) i^{-1}\equiv -p/i*(p\%i)^{-1}\ (mod\ p) i−1≡−p/i∗(p%i)−1 (mod p)
i n v [ i ] = − ( p / i ) ∗ i n v [ p % i ] % p inv[i]=-(p/i)*inv[p\%i]\ \%p inv[i]=−(p/i)∗inv[p%i] %p
int inv[M];
void solv()
{
inv[1]=1;//一定要赋初值
for(int i=2;i<mod;i++)//不能等于mod
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
还可以用递推
阶乘求逆元
先求最大阶乘的逆元(上述任意方法),再回推
因为 ( n − 1 ) ! × n × ( n ! ) − 1 ≡ 1 ( m o d p ) (n-1)!\times n\times (n!)^{-1}\equiv1(mod\ p) (n−1)!×n×(n!)−1≡1(mod p)
所以 ( n − 1 ) ! (n-1)! (n−1)!的逆元为 n ( n ! − 1 ) n(n!^{-1}) n(n!−1)
快速幂
int q_pow(int x,int y,int mod)
{
int ans=1,n=x;
while(y)
{
if(y&1)
ans=(ans*n)%mod;
n=(n*n)%mod;
y>>=1;
}
return ans;
}
中国剩余定理(CRT)
用于求解
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
x
≡
a
3
(
m
o
d
m
3
)
.
.
.
x
≡
a
n
(
m
o
d
m
n
)
\begin{cases} x\equiv a_1\ (mod\ m_1)\\ x\equiv a_2\ (mod\ m_2)\\ x\equiv a_3\ (mod\ m_3)\\ \ \ \ \ .\\ \ \ \ \ .\\ \ \ \ \ .\\ x\equiv a_n\ (mod\ m_n)\\ \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧x≡a1 (mod m1)x≡a2 (mod m2)x≡a3 (mod m3) . . .x≡an (mod mn)
其中
m
1
,
m
2
,
.
.
.
,
m
n
m_1,m_2,...,m_n
m1,m2,...,mn两两互质
设 M = ∏ i = 1 n m i M=\prod_{i=1}^{n}{m_i} M=∏i=1nmi, M i = M / m i M_i=M/m_i Mi=M/mi, M i − 1 ≡ M i ( m o d m i ) M_i^{-1}\equiv M_i\ (mod\ m_i) Mi−1≡Mi (mod mi)
构造解
x ≡ ∑ i = 1 n a i M i M i − 1 ( m o d M ) x\equiv\sum_{i=1}^{n}{a_iM_iM_i^{-1}}\ (mod\ M) x≡∑i=1naiMiMi−1 (mod M)
可证解唯一
扩展CRT
不要求 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn两两互质
设已解出前k-1个方程解为x
且 M = l c m ( m 1 , m 2 , . . . , m n ) M=lcm(m_1,m_2,...,m_n) M=lcm(m1,m2,...,mn),(嫌麻烦可以 M = ∏ i = 1 k m i M=\prod_{i=1}^{k}{m_i} M=∏i=1kmi)
通解为 x + t × M x+t\times M x+t×M(即找出满足后面方程的 t t t)
x + t × M ≡ a k ( m o d m k ) x+t\times M\equiv a_k(mod\ m_k) x+t×M≡ak(mod mk)
t × M ≡ a k − x ( m o d m k ) t\times M\equiv a_k-x(mod\ m_k) t×M≡ak−x(mod mk)
再exgcd求t即可
t = i n v ( M ( M , m k ) , m k ( M , m k ) ) ∗ ( a k − x ) ( M , m k ) % m k ( M , m k ) t=inv(\frac{M}{(M,m_k)} , \frac {m_k}{(M,m_k)} )*\frac{(a_k-x)}{(M,m_k)}\% \frac {m_k}{(M,m_k)} t=inv((M,mk)M,(M,mk)mk)∗(M,mk)(ak−x)%(M,mk)mk
设 x 0 = x + t × M x_0=x+t\times M x0=x+t×M
通解为 x ′ = x 0 + t ′ × l c m ( M , m k ) x^{'}=x_0+t^{'}\times lcm(M,m_k) x′=x0+t′×lcm(M,mk)
一直算下去即可(注意有无解)
例子
积性函数
设 f ( n ) f(n) f(n)为定义在正整数上的函数,若 f ( 1 ) = 1 f(1)=1 f(1)=1,且若正整数a,b互质 f ( a b ) = f ( a ) ∗ f ( b ) f(ab)=f(a)*f(b) f(ab)=f(a)∗f(b)
则 f ( n ) f(n) f(n)为积性函数
若不要求a,b互质,则 f ( n ) f(n) f(n)为完全积性函数
常见积性函数
-
欧拉函数 φ ( n ) \varphi(n) φ(n)为积性函数
且 φ ( n ) = n ∏ p ∣ n ( p 为 素 数 ) ( 1 − 1 p ) \varphi(n)=n\prod_{p|n(p为素数)}{(1-\frac{1}{p})} φ(n)=n∏p∣n(p为素数)(1−p1)
且 ∑ d ∣ n φ ( d ) = n \sum_{d|n}{\varphi(d)}=n ∑d∣nφ(d)=n
-
莫比乌斯函数( μ ( d ) \mu(d) μ(d))
-
约数个数( d ( n ) d(n) d(n)), d ( n ) = ∑ d ∣ n 1 d(n)=\sum_{d|n}{1} d(n)=∑d∣n1
-
约数和函数( σ ( n ) \sigma(n) σ(n)), σ ( n ) = ∑ d ∣ n d \sigma(n)=\sum_{d|n}{d} σ(n)=∑d∣nd
积性函数计算
若有分解式 n = ∏ i = 1 m p i k i n=\prod_{i=1}^{m}{p_i^{ki}} n=∏i=1mpiki
则有 f ( n ) = ∏ i = 1 m f ( p i k i ) f(n)=\prod_{i=1}^{m}{f(p_i^{ki})} f(n)=∏i=1mf(piki)
可在线性筛中算积性函数
质数
定义 π ( n ) \pi(n) π(n)为不大于n的质数个数
则 π ( n ) n log n \pi(n)~\frac{n}{\log n} π(n) lognn
唯一分解定理
设 n > 2 n>2 n>2的整数,则有唯一的分解式
n = ∏ i = 1 m p i k i n=\prod_{i=1}^{m}{p_i^{k_i}} n=∏i=1mpiki
其中 p 1 < p 2 < . . . < p m p_1<p_2<...<p_m p1<p2<...<pm均为质数
- 分解方法一( o ( n ) o(n) o(n))
vector<int> f1(int n)
{
vector<int> f;
for(int i=2;i<=n;i++)
{
while(n/i==0)
{
f.push_back(i);
n/=i;
}
}
return f;
}
-
分解方法二( o ( n ) o(\sqrt{n}) o(n))
vector<int> f2(int n) { vector<int> f; for(int i=2;i*i<=n;i++) { while(n/i==0) { f.push_back(i); n/=i; } } if(n>1) f.push_back(n); return f; }
-
分解方法三( o ( n log n ) o(\frac{n}{\log n}) o(lognn))
int p[Length];//已有素数表 vector<int> f3(int n) { vector<int> f; for(int i=0;i<Length;i++) { if(p[i]*p[i]>n) break; while(n%p[i]==0) { f.push_back(p[i]); } } if(n>1) f.push_back(n); return f; }
质数筛法
Eratosthenes筛法(埃筛)
思想 删去素数的倍数,剩下的即是质数
int p[M];
int cnt=0;
bool flag[M];
void sieve()
{
int i,j;
for(i=2;i<=n;i++)
{
if(!flag[i])
{
p[++cnt]=i;
for(j=i*2;j<=n;j+=i)
flag[j]=true;
}
}
}
欧拉筛(线性筛)
埃筛会重复很多遍,线性筛即是把数表示成其最小质数乘以其他数,最小质数只有一个,则不会重复
int p[M];
int cnt=0;
bool flag[M];
void sieve()
{
int i,j;
for(i=2;i<=n;i++)
{
if(!flag[i])
p[++cnt]=i;
for(j=1;i*p[j]<=n;j++)
{
flag[i*p[j]]=true;
if(i%p[j]==0)
break;
}
}
}
其他
秦九韶算法
f ( x ) = a n x n + a n − 1 x n − 1 + . . . + a 1 x 1 + a 0 f(x)=a_nx^n+a_{n-1}x^{n-1}+...+a_1x^1+a_0 f(x)=anxn+an−1xn−1+...+a1x1+a0
= ( . . . . ( ( a n x + a n − 1 ) x + a n − 2 ) x + . . . + a 1 ) x + a 0 =(....((a_nx+a_{n-1})x+a_{n-2})x+...+a_1)x+a_0 =(....((anx+an−1)x+an−2)x+...+a1)x+a0
例子
例(注意读入取模优化)(1000000007是一个很好的质数)
#include<bits/stdc++.h>
#define ll long long
const int M=1e6+10;
using namespace std;
const int p=1e9+7;
inline ll read()
{
ll sum=0,b=1; char c=getchar();
while(!isdigit(c)){if(c=='-') b=-1; c=getchar();}
while(isdigit(c)){sum=(sum*10+c-48)%p; c=getchar();}
return sum*b;
}
int n,m;
int a[200];
int ans,num[M];
ll judge(int x)
{
int i,j;
ll sum=a[n];
for(i=n-1;i>=0;i--)
{
sum=(sum*x+a[i])%p;
}
return sum;
}
int main()
{
int i,j;
n=read();m=read();
for(i=0;i<=n;i++)
{
a[i]=read();
}
for(i=1;i<=m;i++)
{
if(a[0]%i!=0&&i!=1) continue;
if(judge(i)==0)
{
num[++ans]=i;
}
}
printf("%d\n",ans);
for(i=1;i<=ans;i++)
{
printf("%d\n",num[i]);
}
return 0;
}