浅谈——数论全家桶(一)
chenzefan
·
·
算法·理论
我也不知道写了多久,反正理解了很久。这里仅讲解模板题,虽然也很多。
其实能发好几篇的,但这里直接一次性给出干货。大家好好吸收呀。
写 \LaTeX 写疯了,点赞顶我上去吧。
1 欧拉定理/扩展欧拉定理
我们先来了解一下欧拉函数 \varphi。定义 \varphi(n) 表示 1\sim n 中与 n 互质的数的个数。符号表达,\varphi(n)=\displaystyle\sum_{i=1}^n[\gcd(i,n)=1]。
关于欧拉函数的一些性质。
::::success[性质 1]
若 p 为质数,则 \varphi(p)=p-1。
:::info[证明]
因为除了 p 不与本身互质,其余的 1\sim p-1 均与质数 p 互质。则由定义可得。
:::
::::
::::success[性质 2]
若 p 为质数且 n=p^c,则 \varphi(n)=(p-1)p^{c-1}。
:::info[证明]
考虑正难则反,因为 n 只有一个质因子 p,所以除了 p 的倍数之外其余数都与 n 互质。所以 \varphi(n)=n-\dfrac{n}{p}=n-\dfrac{p^c}{p}=p^c-p^{c-1}=(p-1)p^{c-1}。
:::
::::
:::::success[性质 3]
欧拉函数 \varphi 为积性函数。即若 \gcd(a,b)=1,则 \varphi(a)\varphi(b)=\varphi(ab)。
温馨提示:证明会用到中国剩余定理,谨慎阅读。
::::info[证明]
引理:
:::info[证明]
假设 $\gcd(ab,x)=g>1$,则必有质数 $p>1$ 满足 $p\mid g,p\mid ab,p\mid x$。
因为 p\mid ab 且 p 为质数,则 p\mid a 或 p\mid b。所以 p\mid \gcd(a,x) 或 p\mid \gcd(b,x)。
因为 \gcd(a,x)=\gcd(b,x)=1,所以质数 p 不存在。与假设矛盾,故原命题得证。\gcd(ab,x)=1 成立。
:::
定义集合 S(n)=\{x\mid \gcd(x,n)=1,1\le x\le n\},表示所有不超过 n 的数且与 n 互质的数的集合。显然 |S(n)|=\varphi(n)。
我们易得 \forall 1\le x \le ab,\gcd(ab,x)=1\Rightarrow \gcd(a,x)=\gcd(b,x)=1。
辗转相除法得 \gcd(a,x\bmod a)=\gcd(b,x\bmod b)=1。
就有 S(ab) 到 \{(x,y)\mid x\in S(a),y\in S(b)\} 的映射:\forall 1\le x \le a,1\le y\le b,\gcd(a,x)=\gcd(b,y)=1。
使用中国剩余定理:存在唯一的 1\le z \le ab 使得 z\equiv x\pmod a,z\equiv y\pmod b。
然后就有 \gcd(a,z)=\gcd(b,z)=1。借助引理得 \gcd(ab,z)=1。所以有 \{(x,y)\mid x\in S(a),y\in S(b)\} 到 S(ab) 的映射。故二者为双射(一一对应)。
所以 \{(x,y)\mid x\in S(a),y\in S(b)\} 和 S(ab) 大小一致。
所以 |S(ab)|=|S(a)||S(b)|,即 \varphi(ab)=\varphi(a)\varphi(b)。
故欧拉函数为积性函数。
::::
:::::
::::success[性质 4]
对于任意的 n,分解为 n=p_1^{c_1}p_2^{c_2}\cdots p_x^{c_x},则 \varphi(n)=n\displaystyle\prod_{i=1}^x(1-\dfrac{1}{p_i})。
:::info[证明]
由于欧拉函数为积性函数,所以 \varphi(n)=\varphi(p_1^{c_1})\varphi(p_2^{c_2})\cdots \varphi(p_x^{c_x})。
对于 \varphi(p_i^{c_i}),同样考虑正难则反求解。在 1\sim p_i^{c_i} 中,只有 p_i 的整数倍与 p_i^{c_i} 不互质。那么也就是 p_i,p_i\times2,p_i\times3,\cdots,p_i\times p_i^{c_i-1},也就是 p_i^{c_i-1} 个。所以 \varphi(p_i^{c_i})=p_i^{c_i}-p_i^{c_i-1}=p_i^{c_i}\times(1-\dfrac{1}{p_i})。
然后代入原式,\varphi(n)=\varphi(p_1^{c_1})\varphi(p_2^{c_2})\cdots \varphi(p_x^{c_x})=p_1^{c_1}(1-\dfrac{1}{p_1})p_2^{c_2}(1-\dfrac{1}{p_2})\cdots p_x^{c_x}(1-\dfrac{1}{p_x})=\displaystyle\prod_{i=1}^x p_i^{c_i}(1-\dfrac{1}{p_i})=n\displaystyle\prod_{i=1}^x(1-\dfrac{1}{p_i})。得到结论。
:::
::::
你可以利用积性函数在 O(n) 的复杂度内求出 1\sim n 的所有 \varphi 值。类似线性筛。但是有时候不需要这么多,我们只需要 \varphi(n),就可以利用性质 4 做到 O(\sqrt n)。
然后是欧拉定理。
若 \gcd(a,n)=1,则 a^{\varphi(n)}\equiv 1\pmod n。
::::info[证明]
令 1\sim n 中与 n 互质的 \varphi(n) 个数按顺序排列为 x_1,x_2,\cdots,x_{\varphi(n)}。再令 y_i=ax_i(1\le i \le \varphi(n))。
取 y 中任意两个数相减,即 y_i-y_j=ax_i-ax_j=a(x_i-x_j)。设 x_i-x_j=b,则 y_i-y_j=ab。
显然 |b|=|x_i-x_j|<n,且 \gcd(a,n)=1,所以 ab 不会是 n 的倍数。所以 y_i-y_j 不会是 n 的倍数。所以 y_i 和 y_j 再模 n 意义下不同余。所以 y_i\bmod n 有 \varphi(n) 种余数。
反着辗转相除法得 \gcd(n,y_i\bmod n)=\gcd(y_i,n)=\gcd(ax_i,n)=1。得 n 与 y_i\bmod n 互质。
综上可得 x_i 和 y_i\bmod n 必为映射。所以下式一定成立:
\displaystyle\prod_{i=1}^{\varphi(n)} x_i\equiv\prod_{i=1}^{\varphi(n)}y_i\equiv a^{\varphi(n)}\prod_{i=1}^{\varphi(n)}x_i\pmod n
令 X=\displaystyle\prod_{i=1}^{\varphi(n)}x_i,则 X\equiv a^{\varphi(n)}X\pmod n。
移项变成 (a^{\varphi(n)}-1)X\equiv0\pmod n。由于 \gcd(X,n)=1,所以 a^{\varphi(n)}-1\equiv0\pmod n,所以 a^{\varphi(n)}\equiv1\pmod n。
然后欧拉定理就得证了。
::::
欧拉定理也可以推出费马小定理。这里略。
然后是扩展欧拉定理。特别解决 a 和 n 不互质和 b 过大的情况。
a^b \equiv
\begin{cases}
a^{b \bmod \varphi(n)} & \gcd(a,n)=1 \\
a^b & \gcd(a,n)\neq 1,\, b<\varphi(n) \\
a^{b \bmod \varphi(n)+\varphi(n)} & \gcd(a,n)\neq 1,\, b\ge \varphi(n)
\end{cases}
\pmod n
::::info[证明]
-
第一条:
因为 \gcd(a,n)=1,故根据欧拉定理得 a^{\varphi(n)}\equiv 1\pmod n。
设 b=p \varphi(n)+q,其中 q=b\bmod \varphi(n)(0\le q < \varphi(n))。则 a^b=a^{p\varphi(n)+q}=(a^{\varphi(n)})^p\cdot a^q\equiv1^p\cdot a^q\equiv a^{b\bmod \varphi(n)}\pmod n。
-
第二条易证。
-
第三条:
将 n 分解质因数为 n=p_1^{c_1}p_2^{c_2}\cdots p_x^{c_x},只需对每个 i 证明 a^b \equiv a^{b\bmod \varphi(n)+\varphi(n)}\pmod {p_i^{c_i}} 即可,这可以通过中国剩余定理的性质得到。
于是对 p_i^{c_i} 进行分类。
- 若 \gcd(a,p_i^{c_i})=1,由欧拉定理可得 a^{\varphi(p_i^{c_i})}\equiv1,又因为 \varphi(p_i^{c_i})\mid \varphi(n),故
- 若 \gcd(a,p_i^{c_i})\not=1,则 p_i\mid a。设 a=kp_i,根据欧拉函数的性质 2 得 \varphi(p_i^{c_i})=p_i^{c_i-1}(p_i-1),于是 \varphi(p_i^{c_i})\ge c_i。可以通过数学归纳法证明。
所以 b\ge \varphi(n) \ge \varphi(p_i^{c_i}) \ge c_i。故 p_i^{c_i} 为 a^{b \bmod \varphi(n)+\varphi(n)} 的因数,也是 a^b 的因数。
所以 a^b\equiv a^{b \bmod \varphi(n)+\varphi(n)} \equiv 0\pmod {p_i^{c_i}}。
综上 a^b\equiv a^{b \bmod \varphi(n)+\varphi(n)}\pmod n。
扩展欧拉定理得证。
::::
回归题目,我们发现直接使用扩展欧拉定理即可。注意 b 过大,需要一位一位读入并取模。注意开 long long,然后就做完了。
::::success[代码]
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a,b,mod;
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
signed main(){
scanf("%lld%lld",&a,&mod);a%=mod;
int m=mod,phi=m;
for(int i=2;i*i<=m;i++){
if(m%i!=0) continue;
phi=phi*(i-1)/i;
while(m%i==0) m/=i;
}
if(m!=1) phi=phi*(m-1)/m;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
bool flag=0;
while(c>='0'&&c<='9'){
b=b*10+c-'0';
if(b>phi) flag=1,b%=phi;
c=getchar();
}
if(flag==0) printf("%lld",qpow(a,b));
else printf("%lld",qpow(a,b+phi));
return 0;
}
::::
2 裴蜀定理
裴蜀定理(\text{Bézout}):
对于一个二元一次不定方程 ax+by=c,如果 c 是\gcd(a,b) 的倍数,那么这个方程一定有整数解。
::::info[证明]
简单证明一下。
设 d=\gcd(a,b),已知 d\mid c,令 a'=\frac{a}{d},b'=\frac{b}{d},c'=\frac{c}{d}。此时满足 \gcd(a',b')=1。
原同余方程 ax\equiv c\pmod b 两边同时除以 d,等价于:a'x\equiv c'\pmod {b'}。
取 x_1,x_2\in\{0,1,\cdots,b'-1\},假设
这说明:$x=0,1,\cdots,b'-1$ 对应的 $a'x \bmod b'$ 共 $b'$ 个互不重复的余数。模 $b'$ 的剩余类恰好只有 $0,1,\cdots,b'-1$ 这 $b'$ 种,因此必然存在整数 $x_0\in\{0,1,\cdots,b'-1\}$,使得
$a'x_0\equiv c'\pmod {b'}$,即约分后的方程存在解,原方程随之有解。
于是裴蜀定理得证。
::::
裴蜀定理的逆定理同样成立。
> 对于一个二元一次不定方程 $ax+by=c$,如果这个方程有整数解,那么 $c$ 一定是 $\gcd(a,b)$ 的倍数。
::::info[证明]
令 $d=\gcd(a,b)$,设 $a=a'd,b=b'd,c=c'd+r(0\le r <d)$,原方程变为 $a'dx+b'dy=c'd+r$。
两边同时除以 $d$ 得 $a'x+b'y=c'+\dfrac{r}{d}$,由于方程有整数解,所以 $\dfrac{r}{d}$ 一定为整数,所以 $r=0$。
所以 $c=c'd\Rightarrow d\mid c$。
逆定理得证。
::::
裴蜀定理还有扩展定理。
> 对于一个 $n$ 元一次不定方程 $a_1x_1+a_2x_2+\cdots+a_nx_n=c$,$c$ 是 $\gcd(a_1,a_2,\cdots,a_n)$ 的倍数,是这个方程有整数解的充要条件。
::::info[证明]
可利用 $\gcd(a_1,a_2,\cdots,a_n)=\gcd(\gcd(a_1,a_2,\cdots,a_{n-1}),a_n)$ 进行对 $n$ 的归纳。即可从二元变成 $n$ 元。
扩展定理也可以写成:
$$\displaystyle\sum_{i=1}^na_ix_i=k\gcd\{a_i\}=c$$
::::
回归题目,$S=\displaystyle\sum_{i=1}^n A_i\times X_i$ 通过裴蜀定理的扩展定理可以知道就是 $c$,也就是说,要找一个最小的 $c$ 使方程有解。根据裴蜀定理的扩展定理,取所有数的 $\gcd$ 一定满足条件且最小($k=1$)。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
signed main(){
int n,ans;
scanf("%d%d",&n,&ans);
for(int i=2,x;i<=n;i++)
scanf("%d",&x),ans=__gcd(ans,abs(x));
printf("%d",ans);
return 0;
}
```
::::
# $3$ [$\texttt{exgcd}$](https://www.luogu.com.cn/problem/P5656)
首先欧几里得算法就不多说了,其实就是辗转相除法。公式:$\gcd(a,b)=\gcd(b,a\bmod b)$。
扩展欧几里得算法($\text{exgcd}$),用于求 $ax+by=\gcd(a,b)$ 的整数解。
::::success[$\text{exgcd}$ 原理]
我们来看一个例子。如何求标准形式 $ax+by=\gcd(a,b)$ 的整数解呢?
已知 $\gcd(a,b)=\gcd(b,a\bmod b)$,考虑对方程 $bx+(a \bmod b)y=\gcd(b,a\bmod b)$。同理,在经过约 $\log$ 遍时,我们会得到一个 $b=0$ 的情况。在辗转相除法中,此时的 $\gcd(a,b)=a$。也就是原方程中 $x=1,y=0$ 时得到的 $a=\gcd(a,b)$。
那么,如何通过 $bx+(a\bmod b)y=\gcd(b,a\bmod b)$ 的答案推出 $ax+by=\gcd(a,b)$ 的答案呢?不妨把 $a\bmod b$ 写成 $a-b\cdot\left\lfloor\dfrac{a}{b}\right\rfloor$,那么就是 $bx+(a-b\cdot\left\lfloor\dfrac{a}{b}\right\rfloor) y=\gcd(a,b)$。请注意,此时的形式已经与 $ax+by=\gcd(a,b)$ 相同了。
不妨设 $bx+(a-b\cdot\left\lfloor\dfrac{a}{b}\right\rfloor) y=\gcd(a,b)$ 的解为
$$
\begin{cases}
x=x'\\
y=y'
\end{cases}
$$
在每个位置对应一下,就可以得到原方程的解为:
$$
\begin{cases}
x=y'\\
y=x'-y'\cdot\left\lfloor\dfrac{a}{b}\right\rfloor
\end{cases}
$$
非常自然地递推回上一层。
::::
::::success[代码]
```cpp
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
```
::::
$\text{exgcd}$ 也可以用来求逆元。比费马小定理更万能。
::::success[原理]
考虑构造 $ax+by=c$ 的式子。考虑此时 $ax\equiv 1\pmod b$。那么求 $a$ 的逆元也就是求 $x$。
我们考虑将恒等式变为一个方程,具体如下:
$$ax-bm=1$$
令 $y=-m$ 可得
$$ax+by=1$$
这是显然的。此时跑一遍 $\text{exgcd}$ 可以求出 $x$。但是注意,如果 $\gcd(a,b)\not=1$ 则没有逆元。
::::
回归题目,注意到 $ax+by$ 一定是 $\gcd(a,b)$ 的倍数。无解的情况只有 $\gcd(a,b)\nmid c$。
然后考虑有解。考虑到跑一遍 $\text{exgcd}$ 可以求出 $ax+by=\gcd(a,b)$ 的一组解 $(x,y)$,那么原方程 $ax+by=c$ 的一组解就是 $(x\cdot\dfrac{c}{\gcd(a,b)},y\cdot\dfrac{c}{\gcd(a,b)})$,将其记为 $(X,Y)$。
设 $d=\gcd(a,b),dx=\dfrac{x}{d},dy=\dfrac{y}{d}$,则原方程的整数解可以表示成 $(X+s\cdot dx,Y-s\cdot dy)$,其中 $s$ 取遍所有整数。
当然,如果求正整数解,$s$ 是有范围的。
- 因为 $x\ge1$,即 $X+s\cdot dx\ge1$,解的 $s\ge\dfrac{1-X}{dx}$。由于 $s$ 为整数,所以 $s_{\min}=\left\lceil\dfrac{1-X}{dx}\right\rceil$。
- 因为 $y\ge1$,即 $Y-s\cdot dy\ge1$,解的 $s\le\dfrac{Y-1}{dy}$。由于 $s$ 为整数,所以 $s_{\max}=\left\lfloor\dfrac{Y-1}{dy}\right\rfloor$。
对于 $s_{\max}$ 和 $s_{\min}$ 作比较。前者大于等于后者说明有正整数解,否则没有正整数解。然后输出一些最大最小值判断是 $s$ 取到上界还是下界即可。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,a,b,c;
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
signed main(){
scanf("%lld",&t);
while(t--){
scanf("%lld%lld%lld",&a,&b,&c);
int x=0,y=0,d=exgcd(a,b,x,y);
if(c%d!=0){printf("-1\n");continue;}
int dx=b/d,dy=a/d;
int X=x*c/d,Y=y*c/d;
int minn=ceil(1.0*(1-X)/dx),maxx=floor(1.0*(Y-1)/dy);
if(minn>maxx) printf("%lld %lld\n",X+minn*dx,Y-maxx*dy);
else{
int x1=(X%dx+dx)%dx,y1=(Y%dy+dy)%dy;
if(x1==0) x1=dx;
if(y1==0) y1=dy;
int x2=(c-y1*b)/a,y2=(c-x1*a)/b,cnt=maxx-minn+1;
printf("%lld %lld %lld %lld %lld\n",cnt,x1,y1,x2,y2);
}
}
return 0;
}
```
::::
# $4$ [$\texttt{Lucas}$ 定理](https://www.luogu.com.cn/problem/P3807)/[$\texttt{exLucas}$](https://www.luogu.com.cn/problem/P4720)
首先先看卢卡斯定理($\text{Lucas}$):
> $\displaystyle{n\choose m}\equiv{\left\lfloor\frac{n}{p}\right\rfloor\choose \left\lfloor\frac{m}{p}\right\rfloor}{n\bmod p\choose m\bmod p}\pmod p$。
::::info[证明]
引理:
> 对于任意质数 $p$ 和整数 $x$,都有 $(1+x)^p\equiv1
> +x^p\pmod p$。
:::info[证明]
由二项式展开得 $(1+x)^p=\displaystyle\sum_{i=0}^p{p\choose i}x^i$。
当 $0<i<p$ 时,$\displaystyle{p\choose i}=\dfrac{p!}{i!(p-i)!}$ 均为 $p$ 的倍数,全部整除。而当 $i=p$ 或 $0$ 时,$\displaystyle{p\choose i}=1$。所以 $(1+x)^p\equiv 1\times x^0+1\times x^p=1+x^p\pmod p$。
:::
设 $n=ap+c,m=bp+d(c=n\bmod p,d=m\bmod p)$。
考虑 $(1+x)^n$ 展开:
$$(1+x)^n=(1+x)^{ap+c}=((1+x)^p)^a(1+x)^c$$
由引理得:
$$(1+x)^n\equiv(1+x^p)^a(1+x)^c\pmod p$$
比较两边 $x^m$ 的次数,左边显然为 $\displaystyle{n\choose m}$,而右边当且仅当 $m=bp+d$ 时,$\displaystyle{a\choose b}x^{bp}+{c\choose d}x^d$。则系数为 $\displaystyle{a\choose b}{c\choose d}$。
那么就是 $\displaystyle{n\choose m}\equiv{a\choose b}{c\choose d}\pmod p$,而将 $a,b,c,d$ 换成关于 $n,m$ 的式子即可得到 $\displaystyle{n\choose m}\equiv{\left\lfloor\frac{n}{p}\right\rfloor\choose \left\lfloor\frac{m}{p}\right\rfloor}{n\bmod p\choose m\bmod p}\pmod p$。
卢卡斯定理得证。
::::
回归第一道模板题,显然可以直接对于 $\displaystyle{n\choose m}$ 递归计算,$\displaystyle{n\bmod p\choose m\bmod p}$ 直接计算,$\displaystyle{\left\lfloor\frac{n}{p}\right\rfloor\choose \left\lfloor\frac{m}{p}\right\rfloor}$ 继续递归。注意边界。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,n,m,mod;
int qpow(int a,int b){
if(a==0) return 1;
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
int C(int n,int m){
if(m>n) return 0;
int ans=1;
for(int i=1,j=n;i<=m;i++,j--) ans=ans*j%mod*qpow(i,mod-2)%mod;
return ans;
}
int lucas(int n,int m){
if(n<mod&&m<mod) return C(n,m)%mod;
return C(n%mod,m%mod)*lucas(n/mod,m/mod)%mod;
}
signed main(){
scanf("%lld",&t);
while(t--){
scanf("%lld%lld%lld",&n,&m,&mod);
printf("%lld\n",lucas(n+m,m));
}
return 0;
}
```
::::
然后是扩展卢卡斯定理($\text{exLucas}$)。但是两者算法实现思路并不相同。
求解的题目是一样的。只是可以求解 $p$ 不为质数的情况。
::::success[$\text{exLucas}$ 原理]
温馨提示:推导会用到**中国剩余定理**,谨慎阅读。
常规思路,由于 $p$ 不一定是质数,考虑对其进行质因数分解。设 $p=p_1^{c_1}p_2^{c_2}\cdots p_x^{c_x}$。此时只需求出 $\displaystyle{n\choose m}\bmod p_i^{c_i}$,之后用中国剩余定理可以轻松合并。考虑怎么求?
注意到 $\displaystyle{n\choose m}=\dfrac{n!}{m!(n-m)!}$,于是问题转化成如何快速求解阶乘,直接求肯定是不行的,因为 $p$ 不一定是质数。考虑把含 $p$ 的项提出来。设 $f(n)=n!\bmod p^c$。
$$
\begin{align*}
n!&=1\times2\times3\times\cdots\times n\\
&=(p\times2p\times\cdots\times\left\lfloor\dfrac{n}{p}\right\rfloor p)\times\left\{\Bigg[1\times2\times\cdots\times(p-1)\Bigg]\times\Bigg[(p+1)\times(p+2)\times\cdots\times(2p-1)\Bigg]\times(2p+1)\times(2p+2)\times\cdots\times(\left\lfloor\dfrac{n}{p}\right\rfloor p-1)\times\Bigg[(\left\lfloor\dfrac{n}{p}\right\rfloor p+1)\times(\left\lfloor\dfrac{n}{p}\right\rfloor p+2)\times\cdots\times n\Bigg]\right\}\\
&=p^{\lfloor\frac{n}{p}\rfloor}(1\times2\times\cdots\times\left\lfloor\dfrac{n}{p}\right\rfloor)\times\left\{\Bigg[1\times2\times\cdots\times(p-1)\Bigg]\times\Bigg[(p+1)\times(p+2)\times\cdots\times(2p-1)\Bigg]\times(2p+1)\times(2p+2)\times\cdots\times(\left\lfloor\dfrac{n}{p}\right\rfloor p-1)\times\Bigg[(\left\lfloor\dfrac{n}{p}\right\rfloor p+1)\times(\left\lfloor\dfrac{n}{p}\right\rfloor p+2)\times\cdots\times n\Bigg]\right\}\\
&=p^{\lfloor\frac{n}{p}\rfloor}(\left\lfloor\dfrac{n}{p}\right\rfloor)!\displaystyle\prod_{i=1,p\nmid i}^ni\\
&=p^{\lfloor\frac{n}{p}\rfloor}(\left\lfloor\dfrac{n}{p}\right\rfloor)!(\displaystyle\prod_{i=1,p\nmid i}^{p^c}i)^{\left\lfloor\frac{n}{p^c}\right\rfloor}R
\end{align*}
$$
这个 $R$ 代表最后不到一整段 $p^c$ 的项的积。最后一步的一句是每 $p^c$ 会在 $\bmod p^c$ 的意义下形成一个循环节,共有 $\left\lfloor\dfrac{n}{p^c}\right\rfloor$ 个,所以是 $\left\lfloor\dfrac{n}{p^c}\right\rfloor$ 次方。
那么 $f(n)$ 化简后就会有 $(\left\lfloor\dfrac{n}{p}\right\rfloor)!$,也就是递归到更小的 $f(\left\lfloor\dfrac{n}{p}\right\rfloor)$。在递归的过程中不断累加 $\left\lfloor\dfrac{n}{p}\right\rfloor$ 得到 $g(n)$,表示 $n!$ 中 $p$ 这个因子的数量。
我们成功把 $p$ 从 $n!$ 中分离出来了。
我们考虑原式 $\displaystyle{n\choose m}\equiv \dfrac{f(n)}{f(m)f(n-m)}p^{h(n)-h(m)-h(n-m)}\pmod {p^c}$。然后用 $\text{CRT}$ 合并一下就行。注意此时 $f(n)$ 一定是有逆元的。
::::
回归第二道模板题,做法其实已经讲完了。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n,m,p,prime[N],cnt[N],tot;
vector <int> rest,mod;
int qpow(int a,int b,int mod){
int ans=1;a%=mod;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
int inv(int a,int mod){
int x=0,y=0,d=exgcd(a,mod,x,y);
return (x%mod+mod)%mod;
}
int G(int x,int p){
int ans=0;
while(x) x/=p,ans+=x;
return ans;
}
int F(int n,int p,int pc){
if(n==0) return 1;
int mul=1;
for(int i=1;i<=pc;i++)
if(i%p!=0) mul=mul*i%pc;
int ans=qpow(mul,n/pc,pc);
for(int i=1;i<=n%pc;i++)
if(i%p!=0) ans=ans*i%pc;
return ans*F(n/p,p,pc)%pc;
}
int C_pc(int n,int m,int p,int pc){
if(m>n) return 0;
int fn=F(n,p,pc),fm=F(m,p,pc),fnm=F(n-m,p,pc);
int base=fn*inv(fm,pc)%pc*inv(fnm,pc)%pc;
int exp=G(n,p)-G(m,p)-G(n-m,p);
return base*qpow(p,exp,pc)%pc;
}
int CRT(){
int ans=0,mul=1;
for(auto i:mod) mul*=i;
for(int i=0;i<rest.size();i++){
int Inv=inv(mul/mod[i],mod[i]);
ans=(ans+rest[i]*(mul/mod[i])%mul*Inv%mul)%mul;
}
return ans;
}
int exLucas(){
int i=2,x=p;
while(i*i<=x){
if(x%i==0) prime[++tot]=i;
while(x%i==0) x/=i,cnt[tot]++;
i++;
}
if(x!=1) prime[++tot]=x,cnt[tot]=1;
for(int i=1;i<=tot;i++){
int pc=1;
for(int j=1;j<=cnt[i];j++) pc*=prime[i];
rest.push_back(C_pc(n,m,prime[i],pc));
mod.push_back(pc);
}
return CRT();
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&p);
printf("%lld",exLucas());
return 0;
}
```
::::
# $5$ [中国剩余定理 $\texttt{CRT}$](https://www.luogu.com.cn/problem/P1495)/[$\texttt{exCRT}$](https://www.luogu.com.cn/problem/P4777)
上面一些的结论的推导用到了。这里正式学习一下。
中国剩余定理用于求解如下形式的一元线性同余方程组:
$$
\begin{cases}
x\equiv r_1 & \pmod {m_1} \\
x\equiv r_2 & \pmod {m_2} \\
\cdots\\
x\equiv r_n & \pmod {m_n}
\end{cases}
$$
其中 $m_1,m_2,\cdots,m_n$ 均两两互质。求 $x$ 的最小非负整数解(唯一解)。
::::success[$\text{CRT}$ 实现步骤]
- 先算出 $M=\displaystyle\prod_{i=1}^n m_i$。
- 然后对于第 $i$ 个一元线性同余方程,计算 $p_i=\dfrac{M}{m_i}=\displaystyle\prod_{j=1,j\not=i}^nm_j$,以及 $p_i$ 在模 $m_i$ 意义下的逆元 $p_i^{-1}$(使用 $\text{exgcd}$)。然后计算 $c_i=p_ip_i^{-1}$,这里不能提前对 $m_i$ 取模,仅最终总和对 $M$ 取模。
- 计算唯一解 $x=\displaystyle\sum_{i=1}^nr_ic_i\bmod M$。
::::
::::success[代码]
```cpp
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
int CRT(){
int ans=0,M=1;
for(int i=1;i<=n;i++) M*=m[i];
for(int i=1;i<=n;i++){
int p=M/m[i],x=0,y=0;
exgcd(p,m[i],x,y);
ans=(ans+(r[i]*x*p)%M)%M;
}
return (ans%M+M)%M;
}
```
::::
是不是完全看不懂呢?我们来证明一下为什么这样是对的。
::::info[正确性证明]
证明:以上得到的 $x$ 满足 $\forall i\in[1,n],x\equiv r_i \pmod {m_i}$。
因为 $p_i=\dfrac{M}{m_i}$,所以 $\forall i\not=j,p_i\equiv0\pmod {m_j}$。根据逆元定义得 $p_ip_i^{-1}\equiv1\pmod {m_i}$。
所以
$$
\begin{align*}
x&\equiv\displaystyle\sum_{j=1}^nr_jc_j\pmod {m_i}\\
&\equiv r_ip_ip_i^{-1}\pmod {m_i}\\
&\equiv r_i\pmod {m_i}
\end{align*}
$$
正确性证毕。
::::
::::info[唯一性证明]
证明:满足上述一元线性同余方程组在模 $M$ 意义下解唯一。
假设 $x_1,x_2$ 均是该方程组的整数解。则有:
$$
\begin{cases}
x_1\equiv r_i & \pmod {m_i} \\
x_2\equiv r_i & \pmod {m_i}
\end{cases}
$$
显然对于每个 $i$ 都有 $m_i\mid(x_1-x_2)$。而 $m_i$ 两两互质,所以 $M\mid(x_1-x_2)$。也即 $x_1\equiv x_2\pmod M$。
这就说明原一元线性同余方程组在模 $M$ 的意义下解唯一。
唯一性证毕。
::::
回归第一道模板题。发现 $a_i$ 就是 $m_i$,$b_i$ 就是 $r_i$。套上 $\text{CRT}$ 就行了。至少养母猪的数目就是最小非负整数解,输出 $x$ 即可。使用 `__int128` 防溢出。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int __int128
const int N=1e5+5;
int n,m[N],r[N];
int read(){
int x=0;
char c=getchar();
while(c<'0'&&c>'9') c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
int CRT(){
int ans=0,M=1;
for(int i=1;i<=n;i++) M*=m[i];
for(int i=1;i<=n;i++){
int p=M/m[i],x=0,y=0;
exgcd(p,m[i],x,y);
ans=(ans+(r[i]*x*p)%M)%M;
}
return (ans%M+M)%M;
}
signed main(){
n=read();
for(int i=1;i<=n;i++) m[i]=read(),r[i]=read();
write(CRT());
return 0;
}
```
::::
同样的,我们来学习一下扩展中国剩余定理($\text{exCRT}$)。思路与 $\text{CRT}$ 也不同,可以处理 $m_i$ 不是两两互质的情况。
::::success[$\text{exCRT}$ 原理]
$\text{exCRT}$ 的核心是逐个合并同余方程。
现在考虑两个同余方程:
$$
\begin{cases}
x\equiv a\pmod A\\
x\equiv b\pmod B
\end{cases}
$$
而为了和之后新的同余方程合并,我们需要将这两个同余方程的信息汇总到一个形如 $x\equiv c\pmod C$ 的同余方程。
考虑先将同余方程写成不定方程:
$$
\begin{cases}
x=a+Am\\
x=b-Bn
\end{cases}
$$
其中 $n,m$ 为未知系数,这样构造是为了方便下面的推导。
随即就有 $a+Am=b-Bn$。移项得 $Am+Bn=b-a$。
首先判断一下是否有解。考虑裴蜀定理。如果 $\gcd(A,B)\nmid (b-a)$ 就无解。否则有解。
继续考虑有解的情况。注意到这个方程可以用 $\text{exgcd}$ 求一下,假设求出的特解为 $m_0,n_0$。满足 $Am_0+Bn_0=\gcd(A,B)=d$。
令 $k=\dfrac{b-a}{d}$,则可得:
$$Am_0k+Bn_0k=dk=b-a$$
得到一组特解:
$$
\begin{cases}
m'=m_0\cdot\dfrac{b-a}{d}\\
n'=n_0\cdot\dfrac{b-a}{d}
\end{cases}
$$
然后得到通解:
$$
\begin{cases}
m=m'+\dfrac{B}{d}\cdot s\\
n=n'-\dfrac{A}{d}\cdot s
\end{cases}
$$
其中 $s$ 取遍所有整数。然后将 $m$ 代入 $x=a+Am$ 得:
$$x=a+A(m'+\dfrac{B}{d}s)=(a+Am')+\frac{AB}{d}s$$
记
$$c=a+Am',C=\operatorname{lcm}(A,B)=\dfrac{AB}{\gcd(A,B)}$$
得到合并后的单个同余方程:
$$x \equiv c\pmod C$$
然后每两个同余方程可以合并成新的一个同余方程,再和后面的同余方程继续合并就可以求助解了。
::::
回归第二道模板题。观察到就是扩展中国剩余定理解决的问题,并且题目保证有解。使用 `__int128` 防溢出。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int __int128
int n;
int read(){
int x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else write(x/10),putchar(x%10+'0');
}
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,x,y);
int t=x;x=y,y=t-a/b*y;
return d;
}
signed main(){
n=read();
int C=1,c=0;
for(int i=1;i<=n;i++){
int A=C,a=c,B=read(),b=read();
int x=0,y=0,d=exgcd(A,B,x,y);
int mod=B/d;
x=((b-a)/d*x%mod+mod)%mod;
c=a+A*x,C=A/d*B;
}
write((c%C+C)%C);
return 0;
}
```
::::
# $6$ [$\texttt{BSGS}$](https://www.luogu.com.cn/problem/P3846)/[$\texttt{exBSGS}$](https://www.luogu.com.cn/problem/P4195)
$\text{BSGS}$ 算法,又称大步小步算法,用于求解如下问题:
> 给定质数 $p$,整数 $a,b$,求满足 $a^x\equiv b\pmod p$ 的最小整数 $x$。这个 $x$ 称为 $a$ 在模 $p$ 意义下的离散对数。
::::success[$\text{BSGS}$ 原理]
设块长 $t=\left\lceil \sqrt p\right\rceil,x=i\cdot t-j$,其中 $0\le j<t$。那么回代原式就是 $a^{i\cdot t-j}\equiv b\pmod p$。
恒等号两边同乘 $a^j$ 得:
$$(a^t)^i\equiv b\times a^j\pmod p$$
对于右边 $b\times a^j$,将每一个 $0\le j<t$ 所得的值插入哈希表,方便后续查找。
然后,对于左边 $(a^t)^i$,将每一个 $0\le i \le t$ 所得的值在哈希表中查找。如果找到了,那么必然是最小的。因为 $i$ 小,$j$ 不管怎么取都不会大于后一个块首。
现在我们来分析为什么 $i$ 枚举到 $t$ 就行了。
由于 $p$ 为质数,所以由费马小定理得 $a^{p-1}\equiv1\pmod p$,又众所周知 $a^0=1(a\not=0)$,所以每 $p$ 个一定陷入循环节。也就是说,我们只要验证 $0\le x<p$ 就行。那么代入进去发现 $i$ 最大只能取到 $\left\lceil\sqrt p\right\rceil$。
时间复杂度 $O(\sqrt p)$。已经很优了,但是还有更优的算法——$\text{Pohlig-Hellman}$ 算法。这里不作讲解。
::::
回归第一道模板题。思路在上面说过了。注意特判掉 $n=1$ 就行。
代码变量名按照推导的,不按照原题目的。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int mod,a,b;
unordered_map <int,int> mp;
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
int BSGS(){
b%=mod;
int t=sqrt(mod)+1;
for(int j=0;j<t;j++) mp[b*qpow(a,j)%mod]=j;
a=qpow(a,t);
if(a==0) return b==0?1:-1;
for(int i=1;i<=t;i++){
int now=qpow(a,i),j;
if(!mp[now]&&now!=b) j=-1;
else j=mp[now];
if(j>=0&&i*t-j>=0) return i*t-j;
}
return -1;
}
signed main(){
scanf("%lld%lld%lld",&mod,&a,&b);
if(b==1) return printf("0"),0;
int ans=BSGS();
if(ans==-1) printf("no solution");
else printf("%lld",ans);
return 0;
}
```
::::
$\text{exBSGS}$ 算法可以处理 $p$ 不是质数的情况。具体如下:
::::success[$\text{exBSGS}$ 原理]
首先同余方程 $a^x\equiv b\pmod p$ 可写成 $a^x+kp=b$。
设 $\gcd(a,p)=d$,由于裴蜀定理,当 $d\nmid b$ 时无解,否则有解。考虑有解的情况。
此时 $d\mid b$,考虑等式两边同时除以 $d$ 得到 $a^{x-1}\cdot\dfrac{a}{d}+k\cdot\dfrac{p}{d}=\dfrac{b}{d}$。
那么可以令 $a,p,n$ 都替换成新值然后继续递归。具体的:$a^x\leftarrow a^{x-1},p\leftarrow\dfrac{p}{d},b\leftarrow\dfrac{b}{d}$。
由此一直下去,直到 $\gcd(a,p)=1$。设此时一共递归了 $cnt$ 次,$g=\displaystyle\prod_{i=1}^{cnt} p_i$。
原式化成同余式就是 $a^{x-cnt}\cdot\dfrac{a^{cnt}}{g}\equiv \dfrac{b}{g}\pmod {\dfrac{p}{g}}$。显然此时可以直接跑 $\text{BSGS}$,因为互质。
最后答案加上 $cnt$ 就可以求出 $x$ 了。
::::
回归第二道模板题。思路在上面说过了。注意因为题目没有保证说 $a,b<p$,所以一开始要取模。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a,p,b;
unordered_map <int,int> mp;
int read(){
int x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
int BSGS(int s){
mp.clear();
int t=ceil(sqrt(p)),mul=1;
for(int i=0;i<t;i++) mp[mul*b%p]=i,mul=mul*a%p;
for(int i=0;i<=t;i++){
if(mp.find(s)!=mp.end()&&i*t-mp[s]>=0) return i*t-mp[s];
s=s*mul%p;
}
return -1;
}
int exBSGS(){
a%=p,b%=p;
if(b==1||p==1) return 0;
int cnt=0,s=1;
while(1){
int g=gcd(a,p);
if(g==1) break;
if(b%g!=0) return -1;
cnt++,b/=g,p/=g;
s=(s*a/g)%p;
if(s==b) return cnt;
}
int ans=BSGS(s);
if(ans==-1) return ans;
else return ans+cnt;
}
signed main(){
a=read(),p=read(),b=read();
while(a!=0||p!=0||b!=0){
int ans=exBSGS();
if(ans!=-1) printf("%lld\n",ans);
else printf("No Solution\n");
a=read(),p=read(),b=read();
}
return 0;
}
```
::::
# $7$ [原根](https://www.luogu.com.cn/problem/P6091)
先来了解一下**阶**的定义。
> 若 $a,p$ 互质,满足同余式 $a^n\equiv1\pmod p$ 的最小的 $n$ 被称为模 $p$ 意义下的阶,记作 $\delta_p(a)$。
阶的一些**重要**性质:
::::success[性质 $1$]
> $a,a^2,\cdots,a^{\delta_p(a)}$ 两两互相不同余(在模 $p$ 意义下)。
:::info[证明]
考虑反证法。假设存在 $1\le i<j\le \delta_p(a)$,使得 $a^i\equiv a^j \pmod p$。此时 $1\le j-i< \delta_p(a)$。
因为 $\gcd(a,p)=1$,则存在逆元。同除以 $a^i$ 得 $a^{j-i}\equiv 1 \pmod p$。
回归阶的定义,我们发现,$j-i\ge \delta_p(a)$。与假设矛盾。
假设不成立。原命题得证。
:::
::::
::::success[性质 $2$]
> 若 $a^n\equiv 1\pmod p$,则 $\delta_p(a)\mid n$。
:::info[证明]
设 $n=k\delta_p(a)+r(0\le r<\delta_p(a))$。
如果 $\delta_p(a)\nmid n$,则 $r\not=0$,也就是 $r>0$。此时:
$$a^r\equiv a^r\cdot(a^{\delta_p(a)})^k\equiv a^n\equiv1\pmod p$$
此时因为 $r<\delta_p(a)$,所以 $r=0$,与 $\delta_p(a)\nmid n$ 矛盾。
所以 $\delta_p(a)\mid n$,原命题得证。
:::
::::
::::success[性质 $3$]
> 若 $\gcd(a,p)=\gcd(b,p)=1$,则 $\delta_p(ab)=\delta_p(a)\delta_p(b)\Leftrightarrow \gcd(\delta_p(a),\delta_p(b))=1$。
:::info[证明(前推后)]
由阶的定义得 $a^{\delta_p(a)}\equiv b^{\delta_p(b)}\equiv1\pmod p$。
设 $L=\operatorname{lcm}(\delta_p(a),\delta_p(b))$。则通过上式可推 $a^L\equiv b^L\equiv1\pmod p$。
所以 $(ab)^L\equiv1\pmod p$。
根据性质 $2$ 得:
$$\delta_p(ab)\mid L\Rightarrow \delta_p(a)\delta_p(b)\mid L\Rightarrow \gcd(\delta_p(a),\delta_p(b))=1$$
:::
:::info[证明(后推前)]
由定义得 $(ab)^{\delta_p(ab)}\equiv1\pmod p$。则
$$
((ab)^{\delta_p(ab)})^{\delta_p(b)}\equiv1\pmod p\\
(a^{\delta_p(ab)})^{\delta_p(b)}(b^{\delta_p(ab)})^{\delta_p(b)}\equiv1\pmod p\\
(a^{\delta_p(ab)})^{\delta_p(b)}(b^{\delta_p(b)})^{\delta_p(ab)}\equiv1\pmod p\\
(a^{\delta_p(ab)})^{\delta_p(b)}\equiv1\pmod p
$$
根据性质 $2$ 得 $\delta_p(a)\mid\delta_p(ab)\delta_p(b)$。
因为 $\gcd(\delta_p(a),\delta_p(b))=1$,所以 $\delta_p(a)\mid\delta_p(ab)$。
同理 $\delta_p(b)\mid\delta_p(ab)$。所以 $\delta_p(a)\delta_p(b)\mid\delta_p(ab)$。
另一方面,$(ab)^{\delta_p(a)\delta_p(b)}\equiv (a^{\delta_p(a)})^{\delta_p(b)}(b^{\delta_p(b)})^{\delta_p(a)}\equiv1\pmod p$。
根据性质 $2$ 得 $\delta_p(ab)\mid\delta_p(a)\delta_p(b)$。
综上可得 $\delta_p(a)\delta_p(b)=\delta_p(ab)$。
:::
::::
::::success[性质 $4$]
> $\delta_p(a^k)=\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)}$。
:::info[证明]
首先注意到:
$$a^{k\delta_p(a^k)}\equiv(a^k)^{\delta_p(a^k)}\equiv1\pmod p$$
根据性质 $2$ 得 $\delta_p(a)\mid (k\delta_p(a^k))$。
所以
$$\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)}\mid\dfrac{k\delta_p(a^k)}{\gcd(\delta_p(a),k)}$$。
然后显然 $\gcd(\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)},\dfrac{k}{\gcd(\delta_p(a),k)})=1$,也就是去除 $\dfrac{k}{\gcd(\delta_p(a),k)}$ 对整除关系没有影响。于是取出后变成了 $\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)}\mid \delta_p(a^k)$。
由 $a^{\delta_p(a)}\equiv1\pmod p$ 可得:
$$(a^k)^{\frac{\delta_p(a)}{\gcd(\delta_p(a),k)}}\equiv (a^{\delta_p(a)})^{\frac{k}{\gcd(\delta_p(a),k)}}\equiv1\pmod p$$
根据性质 $2$ 可得:
$$\delta_p(a^k)\mid\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)}$$
综上,$\delta_p(a^k)=\dfrac{\delta_p(a)}{\gcd(\delta_p(a),k)}$。
:::
::::
::::success[性质 $5$]
> $\delta_p(a)$ 一定存在,且 $\delta_p(a)\mid\varphi(p)$。
:::info[证明]
根据欧拉定理,$a^{\varphi(p)}\equiv 1\pmod p$。
由依据性质 $2$ 得,阶一定存在,且是 $\varphi(p)$ 的因数。
:::
::::
然后是**原根**的定义。
> 正整数 $g$ 是正整数 $n$ 的原根,当且仅当 $1\le g\le n−1$,且 $g$ 模 $n$ 的阶为 $\varphi(n)$。
原根的一些**重要**性质:
::::success[性质 $1$]
> 若 $n$ 存在原根 $g$,则 $g,g^2,g^3,\cdots,g^{\varphi(n)}$ 构成模 $n$ 的简化剩余系。
:::info[证明]
首先,$g$ 是模 $n$ 的原根,故 $\gcd(g,n)=1$。于是对任意正整数 $k$,$\gcd(g^k,n)=1$,因此 $g,g^2,g^3,\cdots,g^{\varphi(n)}$ 中每个数均与 $n$ 互质。
再证明这 $\varphi(n)$ 个数模 $n$ 两两不同余。
假设存在 $1\le i<j\le \varphi(n)$,使得
$$g^i \equiv g^j \pmod{n}$$
由 $\gcd(g^i,n)=1$,两边消去 $g^i$ 得
$$g^{j-i}\equiv 1 \pmod{n}$$
注意到 $0<j-i<\varphi(n)$,而 $\delta_n(g)=\varphi(n)$,且根据阶的定义,不可能存在比 $\delta_n(g)$ 更小的 $x$ 满足 $a^x\equiv1\pmod n$。与假设矛盾。
所以 $g,g^2,g^3,\cdots,g^{\varphi(n)}$ 构成模 $n$ 的简化剩余系。
:::
::::
:::::success[性质 $2$]
> 若 $n$ 存在原根,那么对于任意 $\varphi(n)$ 的因子 $d$,模 $n$ 的 $d$ 阶元素个数为 $\varphi(d)$。
::::info[证明]
设 $d'=\dfrac{\varphi(n)}{d}$,根据阶的性质 $4$ 得:
$$\delta_n(g^{d'})=\dfrac{\delta_n(g)}{\gcd(d',\delta_n(g))}=\dfrac{\varphi(n)}{\gcd(d',\varphi(n))}=\dfrac{\varphi(n)}{\gcd(\frac{\varphi(n)}{d},\varphi(n))}=d。$$
设正整数 $k$。
- 当 $k\le d$ 时,根据阶的性质 $4$,当且仅当 $\gcd(k,d)=1$ 时,$\delta_n(g^{kd'})=\delta_n((g^{d'})^k)=\dfrac{\delta_n(g^{d'})}{\gcd(k,\delta_n(g^{d'}))} = \dfrac{d}{\gcd(k,d)}=d$,这样的 $k$ 有 $\varphi(d)$ 个,因为 $\varphi$ 的定义。
根据性质 $1$,$g^{kd'}$ 模 $n$ 一定互不相同,所以模 $n$ 的 $d$ 阶的元素至少有 $\varphi(d)$ 个。
- 当 $k>d$ 时,设 $k=qd+r(r=k\bmod d,0\le r<d)$。
两边同乘 $d'$ 得:
$$kd'=qdd'+rd'$$
因为 $dd'=d\cdot\dfrac{\varphi(n)}{d}=\varphi(n)$。所以:
$$kd'=q\varphi(n)+rd'$$
对 $\varphi(n)$ 取模就有:
$$kd'\equiv rd'\equiv (k\bmod d)d'\pmod {\varphi(n)}$$
显然根据欧拉定理有 $g^{\varphi(n)}\equiv1\pmod n$,则有:
$$g^{kd'}=g^{q\varphi(n)+rd'}=(g^{\varphi(n)})^q \cdot g^{rd'} \equiv g^{rd'}\pmod n$$
换句话说就是 $k>d$ 时,$g^{kd'}$ 的值一定会和某个 $1\le r\le d$ 的 $g^{rd'}$ 重复,不会产生新元素。就可以归纳进 $k\le d$ 的情况。
综上,模 $n$ 的 $d$ 阶元素个数为 $\varphi(d)$。
::::
::::success[推论]
> 若 $n$ 存在原根,那么原根的个数为 $\varphi(\varphi(n))$。
:::info[证明]
根据定义,$n$ 的原根就是模 $n$ 的 $\varphi(n)$ 阶元素。
结合性质 $2$ 可得原根个数就是 $\varphi(\varphi(n))$。
:::
::::
:::::
::::success[性质 $3$]
> 质数 $p$ 的最小原根 $g$,满足 $g=\Omega(\log p)=O(p^{0.25+\varepsilon}),\varepsilon>0$。
不证。
::::
::::success[性质 $4$]
> 设 $p$ 为奇质数,$g$ 是模 $p$ 的原根,则:
> - $g$ 与 $g+p$ 中必有一个是模 $p^\alpha$ 的原根。
> - $g$ 与 $g+p^\alpha$ 中的奇数是模 $2p^\alpha$ 的原根。
不证。
::::
原根的一些定理:
:::::success[原根判别定理]
> 设整数 $g,n$ 满足 $\gcd(g,n)=1$,则 $g$ 是模 $n$ 的原根当且仅当:对 $\varphi(n)$ 的每一个质因子 $p$,均有:
> $$g^{\frac{\varphi(n)}{p}} \not\equiv 1 \pmod n$$
::::info[证明]
记 $t=\delta_n(g)$,由欧拉定理知 $t\mid \varphi(n)$。
:::info[证明(必要性)]
若 $g$ 是原根,则 $t=\varphi(n)$。任取 $\varphi(n)$ 的质因子 $p$。
假设 $g^{\frac{\varphi(n)}{p}}\equiv 1\pmod n$,则 $t\mid \dfrac{\varphi(n)}{p}$,即 $\varphi(n)\mid \dfrac{\varphi(n)}{p}$,显然矛盾。
故必有 $g^{\frac{\varphi(n)}{p}} \not\equiv 1 \pmod n$。
:::
:::info[证明(充分性)]
假设对所有 $\varphi(n)$ 的质因子 $p$ 都有 $g^{\frac{\varphi(n)}{p}} \not\equiv 1 \pmod n$。
反设 $t<\varphi(n)$,由 $t\mid\varphi(n)$,令 $\varphi(n)=tk\ (k>1)$,取 $k$ 的一个质因子 $p$,则 $p\mid\varphi(n)$ 且 $t\mid \dfrac{\varphi(n)}{p}$,推出 $g^{\frac{\varphi(n)}{p}}\equiv 1\pmod n$,与条件矛盾。
因此 $t=\varphi(n)$,$g$ 为模 $n$ 原根。
:::
::::
:::::
::::success[原根个数定理]
同性质 $2$ 推论。
> 若 $n$ 存在原根,那么原根的个数为 $\varphi(\varphi(n))$。
:::info[证明]
根据定义,$n$ 的原根就是模 $n$ 的 $\varphi(n)$ 阶元素。
结合性质 $2$ 可得原根个数就是 $\varphi(\varphi(n))$。
:::
::::
::::success[原根存在定理]
> 整数 $n$ 存在原根当且仅当 $n=2,4,p^k,2p^k$,其中 $p$ 是不为 $2$ 的质数,或者说是奇质数。
不证。
::::
过程中未证明的部分是因为证明非常困难,而非笔者不想证。
回归题目。根据性质 $3$,我们发现最小的原根非常的小。约等于 $\sqrt[4]{n}$。直接暴力枚举最小原根 $g$,再枚举指数 $s$,若 $\gcd(s,\varphi(n))=1$,则 $g^s$ 为原根。时间复杂度很小的。
::::success[代码]
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int t,n,d,p[N],phi[N],cnt;
bool vis[N];
vector <int> vec,ans;
void init(){//线性筛+欧拉函数
phi[1]=1;
for(int i=2;i<=N-5;i++){
if(!vis[i]) p[++cnt]=i,phi[i]=i-1;
for(int j=1;j<=cnt&&p[j]*i<=N-5;j++){
vis[p[j]*i]=1;
phi[p[j]*i]=phi[i]*(p[j]-1);
if(i%p[j]==0){phi[p[j]*i]=p[j]*phi[i];break;}
}
}
}
int qpow(int a,int b,int mod){
int ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod,b>>=1;
}
return ans;
}
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
bool check(int x){//原根存在定理
if(x==2||x==4) return 1;
if(x%2==0) x/=2;
int j=2;
while(p[j]<=x){
if(x%p[j]==0){
while(x%p[j]==0) x/=p[j];
return x==1;
}
j++;
}
return 0;
}
signed main(){
init();
scanf("%lld",&t);
while(t--){
scanf("%lld%lld",&n,&d);
if(!check(n)){printf("0\n\n");continue;}
vec.clear(),ans.clear();
int x=phi[n],j=2;
while(j*j<=x){
if(x%j==0) vec.push_back(j);
while(x%j==0) x/=j;
j++;
}
if(x!=1) vec.push_back(x);
int g=1;
while(1){//找最小原根g
if(gcd(g,n)!=1){g++;continue;}//i不存在模n的阶
bool flag=1;
for(auto i:vec)//判定原根
if(qpow(g,phi[n]/i,n)==1){flag=0;break;}
if(flag) break;g++;
}
int s=1,G=1;//枚举指数s并找其他原根G
while(ans.size()<phi[phi[n]]){//原根个数定理
G=G*g%n;
if(gcd(phi[n],s)==1) ans.push_back(G);
s++;
}
sort(ans.begin(),ans.end());
printf("%lld\n",phi[phi[n]]);
for(int i=d-1;i<ans.size();i+=d) printf("%lld ",ans[i]);
printf("\n");
}
return 0;
}
```
::::
---
这篇就先讲这么多。
另外的资料请见 [OI-wiki](https://oi-wiki.org/)。