数论小结
求 \(N\) 的正约数集合——试除法
若 \(d\geq N\) 是 \(N\) 的约数,则 \(N/d\leq \sqrt(N)\) 也是 \(N\) 的约数。换言之,约数总是成对出现(除了对于完全平方数, \(\sqrt(N)\) 会单独出现)。
因此只需要扫描 \(d=1\sim \sqrt{N}\), 尝试 \(d\) 能否整除 \(N\) ,若能整除,则 \(N/d\) 也是 \(N\) 的约数,时间复杂度为 \(O(\sqrt{N})\)
推论:一个整数 \(N\) 的约数个数上界为 \(2\sqrt{N}\)
int inv[N],m=0;
for(int i=1;i*i<=n;i++)
{
if(n%i==0)
{
inv[++m]=i;
if(i!=n/i) inv[++m]=n/i;
}
}
求 \(1\sim N\) 每个数的正约数集合——倍数法
对于每个数 \(d\) ,\(1\sim N\) 中以 \(d\) 为约数的数就是 \(d\) 的倍数 \(d,2d,3d,\ldots,\lfloor N/d\rfloor*d\)。
时间复杂度为 \(O(N+N/2+N/3+\ldots+N/N)=O(N\log N)\)
推论:\(1\sim N\) 每个数的约数个数的总和大约为 \(N\log N\)
vector<int> inv[N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n/i;j++)
inv[i*j].push_back(i);
素数判断
单一素数判断
inline bool is_prime(int n)
{
if(n<=1) return 0;
for(int i=2;i*i<=n;i++) if(n%i==0) return 0;
return 1;
}
埃氏筛
int prime[N+5];
bool vis[N+5];
inline int E_sieve(int n)
{
int k=0;
for(int i=0;i<=n;i++) vis[i]=0;
for(int i=2;i<=n;i++) if(!vis[i])
{
prime[k++]=i;
for(int j=2*i;j<=n;j+=i) vis[j]=1;
}
return k;
}
线性筛
原理:一个合数肯定有一个最小质因数,让每个合数只为它的最小质因数筛选一次,以达到不重复筛的目的
具体操作:
- 逐一检查 \(2\sim n\) 的所有数,第一个检查的是 \(2\),它是第一个素数
- 当检查到第 \(i\) 个数时,利用已经求得的素数去筛掉对应的合数 \(x\) ,而且是用 \(x\) 的最小质因数去筛
int n,v[N];
vector<int> pri;
void shai()
{
for(int i=2;i<=n;i++)
{
if(v[i]==0)
{
v[i]=i;
pri.push_back(i);
}
for(auto x:pri)
{
if(x>v[i]||x>n/i) break;
v[i*x]=x;
}
}
}
int pri[N];
bool vis[N];
inline int euler_sieve(int n)
{
int cnt=0;
memset(vis,0,sizeof(vis));
memset(pri,0,sizeof(pri));
for(int i=2;i<=n;i++)
{
if(!vis[i]) pre[cnt++]=i;
for(int j=0;j<cnt;j++)
{
if(i*pri[j]>n) break;
vis[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
return cnt;
}
gcd与lcm
gcd
gcd性质
- \(\gcd(a,b)=\gcd(a,a+b)=\gcd(a,k*a+b)\)
- \(\gcd(ka,kb)=k*\gcd(a,b)\)
- \(\gcd(a,b,c)=\gcd[\gcd(a,b),c]\)
- \(\gcd(a+cb,b)=\gcd(a,b)\)
- 若 \(\gcd(a,b)=d\),则 \(\gcd(a/d,b/d)=1\),即 \(a/d\) 与 \(b/d\) 互素。
欧几里得算法
用辗转相除法求gcd,即 \(\gcd(a,b)=\gcd(b,a\mod b)\)
inline int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline int gcd(int a,int b)
{
int r;
while(b!=a){r=b;b=a%b;a=r;}
return a;
}
更相减损法
\(\gcd(a,b)=\gcd(b,a-b)=\gcd(a,a-b)\)
inline int gcd(int a,int b)
{
while(a!=b)
{
if(a>b) a-=b;
else b-=a;
}
return a;
}
Stein算法
Stein算法是更相减损法的改进,求 \(\gcd(a,b)\) 时,可以分为几种情况进行优化:
- \(a\) 和 \(b\) 都是偶数,\(\gcd(a,b)=2\gcd(a/2,b/2)\)
- \(a\) 是奇数,\(b\) 是偶数。根据原理,若 \(k\) 与 \(y\) 互为质数,有\(\gcd(kx,y)=\gcd(x,y)\),所以\(\gcd(a,b)=\gcd(a,b/2)\)
- \(a\) 是偶数,\(b\) 是奇数。\(\gcd(a,b)=\gcd(a/2,b)\)
- \(a\) 和 \(b\) 都是奇数,\(\gcd(a,b)=\gcd((a+b)/2,(a-b)/2)\),或 \(\gcd(a,b)=\gcd(|a-b|,\min(a,b))\)
inline int gcd(int a,int b)
{
if(a==0) return b;
if(b==0) return a;
if(a&1==0&&b&1==0) return 2*gcd(a>>1,b>>1);
else if(a&1==0) return gcd(a>>1,b);
else if(b&1==0) return gcd(a,b>>1);
else return gcd((a+b)/2,(a-b)/2);
// else return gcd(abs(a-b),min(a,b));
}
lcm
inline int lcm(int a,int b){return a/gcd(a,b)*b;}
exgcd
求解 \(ax+by=c\)
\(ax+by=\gcd(a,b)\) 一定有解。
\(ax+by=c\)一定有解的前提是 \(c\%\gcd(a,b)=0\)
解\(ax+by=\gcd(a,b)\)
- \(b=0,\gcd(a,b)=a\),此时 \(x=1,y=0\)
- \(b\neq 0,\gcd(a,b)=\gcd(b,a\%b)\),
我们可以先求 \(bx+(a\%b)y=\gcd(b,a\%b)\)的解 \(\begin{cases}x=x_0\\ y=y_0\end{cases}\),然后回带
特解为 \(x_0,y_0\),通解为 \(x=x_0+\frac{b}{g}k,y=y_0-\frac{a}{g}k(g=\gcd(a,b),k\in \Z)\)
解 \(ax+by=c\)
设 \(g=\gcd(a,b)\)
先求 \(ax_0+by_0=g\)
如果 \(c\%g\neq 0\),则方程无整数解
如果 \(c\%g= 0\),则一个特解为 \(x=x_0\cdot\frac{c}{g},y=y_0\cdot\frac{c}{g}\)
通解:
解 \(ax\equiv b(\bmod m)\)
设 \(g=\gcd(a,b)\)
\(ax\equiv b(\bmod m)\Rightarrow ax+my=b\)
求逆元
\(ax\equiv 1(\bmod m)\Rightarrow ax+by=1\)
代码
递归
inline int exgcd(int a,int b,int &x,int &y)
{
if(b==0){x=1,y=0;return a;}
int g=exgcd(b,a%b,y,x);
y-=a/b*x;
return g;
}
迭代
inline int exgcd(int a,int b,int &x,int &y)
{
int x0=1,y0=0,x1=0,y1=1,q,tmp;
while(b)
{
q=a/b;
tmp=a-q*b;
a=b;b=tmp;
tmp=x0-q*x1;
x0=x1;x1=tmp;
tmp=y0-q*y1;
y0=y1;y1=tmp;
}
x=x0;y=y0;
return a;
}
CRT
求解同余方程组
若 \(m_1,m_2,\cdots,m_k\) 两两互质,即对于任意 \(i\neq j\),都有 \(\gcd(m_i,m_j)=1\),则方程组在模 \(M=\prod_{i=1}^km_i\) 下有唯一解
令 \(M=\prod_{i=1}^km_i\),对于 \(i\),令 \(M_i=\frac{M}{m_i}\),因为 \(m_i\) 与其他模数互质,所以 \(\gcd(M_i,m_i)=1\),因此 \(M_i\) 在模 \(m_i\) 下存在逆元,记作 \(t_i=M_i^{-1}(\bmod m_i)\),
即 \(M_it_i\equiv 1(\bmod m_i)\Rightarrow M_it_i+m_ix=1\)
方程组的一个解为 \(x\equiv \sum_{i=1}^ka_iM_it_i(\bmod M)\),\(x_0=\sum_{i=1}^ka_i,M_it_i\)
所有解为 \(x=x_0+kM,k\in \Z\),\(x_0\) 是上述式子模 \(M\) 的最小正整数
为什么成立: 对于任意 \(j\) ,考察上述式子模 \(m_j\)
当 \(i\neq j\) 时,\(M_i=\frac{M}{m_i}\) 包含了因子 \(m_j\),所以 \(M_i\equiv 0(\bmod m_i)\),对应项为 \(0\)
当 \(i=j\) 时,\(M_jt_j\equiv (\bmod m_i)\),所以该项为 \(a_j*1=a_j\)
\(\because(a+b)\%m=(a\%m+b\%m)\%m\)
\(\therefore\) 设 \(b_i=a_iM_it_i,x_0=b_1+b_2+\cdots+b_n\)
考虑第 \(j\) 个式子 \(x\equiv a_j(\bmod m_j)\)
\[\begin{split}x_0\%m_j&=(b_1+b_2+\cdots+b_n)\%m_j\\&=(b_1\%m_j+b_2\%m_j+\cdots+b_n\%m_j)\%m_j\end{split} \]\[\begin{cases}b_i\%m_j=0\quad i\neq j\\b_i\%m_j=a_i\quad i=j\end{cases} \]\(\therefore\) 满足 \(x_0\%m_i=a_i,\forall i\in[1,n]\)
using i128=__int128_t
inline int crt(const vector<int> &a,const vector<int> &m,int n)
{
int M=1,ans=0;
for(int i=1;i<=n;i++) M*=m[i];
for(int i=1;i<=n;i++)
{
int Mi=M/m[i],inv,tmp;
exgcd(Mi,m[i],inv,tmp);
inv=(inv%m[i]+m[i])%m[i];
ans=(ans+(i128)a[i]*Mi*inv)%M;
}
return ans;
}
ExCRT:模数不一定互质
1. 核心思想:两两合并
假设已经求出前 \(i-1\) 个方程的解,可以表示为:
其中 \(M = \operatorname{lcm}(m_1, m_2, \dots, m_{i-1})\)。
现在加入第 \(i\) 个方程:
因为 \(x = A + M t\),代入得:
即:
这是一个关于 \(t\) 的线性同余方程。
2. 判断有解条件
令:
线性同余方程 \(M t \equiv a_i - A \pmod{m_i}\) 有解,当且仅当:
即:
如果无解,则整个同余方程组无解。
3. 求解 \(t\)
若有解,将方程两边同时除以 \(g\):
此时:
所以 \(\frac{M}{g}\) 在模 \(\frac{m_i}{g}\) 下存在逆元。用扩展欧几里得求出逆元 \(\text{inv}\),则:
取最小非负解 \(t_0\),则新的解为:
新的模数为:
于是前 \(i\) 个方程等价于:
重复上述过程,直到合并完所有方程。
证明:判断有解条件
我们证明:线性同余方程
有整数解,当且仅当
其中 \(b=a_i-A\),\(m=m_i\)。
1. 必要性
假设存在整数 \(t\) 满足
则存在整数 \(k\),使得
即
设
因为 \(g \mid M\) 且 \(g \mid m\),所以
因此
所以若方程有解,必有
必要性得证。
2. 充分性
假设
由裴蜀定理,存在整数 \(u,v\),使得
两边同时乘以 \(\dfrac{b}{g}\),得到
令
则有
即
所以 \(t\) 就是方程的一个整数解。
因此,只要 \(g \mid b\),方程就一定有解。
3. 结论
综合必要性和充分性,线性同余方程
有整数解,当且仅当
在 ExCRT 中,\(M\) 是前 \(i-1\) 个方程解对应的模数,\(m=m_i\),\(b=a_i-A\)。因此判断第 \(i\) 个方程能否与前面的解合并,条件就是:
如果这个条件不满足,则整个同余方程组无解。
\(\{A,M\}\) 表示 \(x\equiv A(\bmod M)\),无解返回 \(\{-1,-1\}\)
using i128=__int128_t
inline pair<int,int> excrt(const vector<int> &a,const vector<int> &m,int n)
{
int A=a[1],M=m[1];
for(int i=2;i<=n;i++)
{
int x,y;
int g=exgcd(M,m[i],x,y);
if((a[i]-A)%g) return {-1,-1};
int m1=m[i]/g;
int t=((a[i]-A)/g%m1+m1)%m1;
int inv,tmp;
exgcd(M/g,m1,inv,tmp);
inv=(inv%m1+m1)%m1;
int k=t*inv%m1;
int newM=(i128)M/g*m[i];
A=(int)(((i128)A+(i128)M*k)%newM);
M=newM;
A=(A%M+M)%M;
}
return {A,M};
}
本文来自博客园,作者:Aries2011,转载请注明原文链接:https://www.cnblogs.com/Aries2011/p/23483664

浙公网安备 33010602011771号