数论
这不是一个很详细的总的数论合集,只是一个比较简略,特别是证明很简略的类似笔记一样的东西。
数论分块
数论分块是相对基础的算法。其主要作用是化简带向上或向下取整的和式。
有引理:
当然这只是个引理。
数论分块的主要思想是 \(\left \lfloor \frac{n}{d} \right \rfloor\) 只有 \(O(\sqrt n)\) 种取值,我们将取值相同的放在一起算就可以降低复杂度。
对于 \(i\in [1,n]\),与 $\left \lfloor \frac{n}{i} \right \rfloor $ 值相同且最大的 \(j\) 是 $\left \lfloor \frac{n}{\left \lfloor \frac{n}{i} \right \rfloor } \right \rfloor $。
因此我们就可以用循环的形式来求解类似 $\sum_i^nf(i)\left \lfloor \frac{n}{i} \right \rfloor $ 的和式。当然,我们首先需要知道 \(f(i)\) 的前缀和。
欧拉函数
定义
- 对于一个数n,\(\varphi(n)\)是小于n的所有正整数中与n互质的数的个数
- 定义式:$\varphi(n)=n*\frac{p_{1}-1}{p_{1}} * \frac{p_{2}-1}{p_{2}} \cdots $
\(p_{i}\)是n的所有质因子 注意:一种质因子只能乘一次 - 定义域:\(N^{*}\)
性质
- \(\varphi(1)=1\)
- 对于一个质数p,\(\varphi(p)=p-1\)
- 如果p、q为质数,则有\(\varphi(p * q)=\varphi(p) * \varphi(p)=(p-1) * (q-1)\),即欧拉函数是积性的
- 如果p为质数,则有\(\varphi(p^{k})=p^{k}-p^{k-1}\)
- 对于正整数n,有\(\sum_{h|n}\varphi(h)=n\)
- 对于互质的两个数a、b,\(\varphi(a * b)=\varphi(a) * \varphi(b)\)
- 如果\(a|b\),则\(\varphi(a * b)=\varphi(b) * a\)
求解
单个欧拉函数的求解
- 直接使用定义式暴力枚举每个质因子,时间复杂度\(O(\sqrt{n})\)
cin>>n;
int res=n;
for(int i=2;i*i<=n;i++)
{
if(!(n%i))
{
res=res*(i-1)/i;
while(!(n%i)) n/=i;
}
}
if(n>1) res=res*(n-1)/n;//注意,这一行必须加,这里证明还有一个质因数没算
欧拉筛线性求欧拉函数
- 通过欧拉筛,筛出质数后运用性质5、6求出线性范围内的所有数的欧拉函数
vector <int> q;
int tot=0;
cin>>n;//范围
vis[0]=vis[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])//如果是质数,则初始化其欧拉函数并将其压入q中
{
phi[i]=i-1;
q.push_back(i),tot++;
}
for(int j=0;j<tot&&i*q[j]<=n;j++)//枚举所有质数
{
vis[i*q[j]]=1;
if(!i%q[j])
{
phi[i*q[j]]=phi[i]*q[j];//性质6
break;//i已经包含了q[j]的所有质因子,再继续枚举一定全是重复的
}
else phi[i*q[j]]=phi[i]*phi[q[j]];//性质5.由于q[j]是质数,所以如果i不整除q[j],因此二者一定互质
}
}
例题
- Mr.Hu 最近在研究等比数列,即形如:
$ a^{0} , a^{1} , a^{2} , \cdots , a^{n} , \cdots $
现在,Mr.Hu 想知道,对于给定的非负整数 a,上面这个无穷数列在模 mod 意义下有多少项是本质不同的。(保证 gcd(a, mod) = 1)。
对于 30% 的数据,0 ≤ a ≤ \(10^{3}\),1 ≤ mod ≤ \(10^{3}\);
对于 100% 的数据,0 ≤ a ≤ 2 × \(10^{9}\),1 ≤ mod ≤ 2 × \(10^{9}\),且保证 gcd(a, mod) = 1,1 ≤ T ≤ 100。 - 设\(a^{x}\equiv 1 (mod p)\),则显然再继续乘是循环的,因此求出最小的x后,由于指数是从0开始的,因此就有x种不同的答案
由于欧拉定理\(a^{\varphi(p)}\equiv 1(mod p)\),因此在\(x=\varphi(p)\)时是一定有解的
现在的问题就是如何去由这个特解去求最小解
这里有一个很暴力的做法,就是去枚举\(\varphi(p)\)的所有因数。暴力去check这个因数是否合法
由于因数最多只有$ \sqrt{p} $个,因此时间复杂度为 $ O ( T\sqrt{p}log_{p} ) $
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ksm(ll x,ll k,ll p)
{
ll res=1;
while(k)
{
if(k&1) res=res*x%p;
k>>=1;x=x*x;x%=p;
}
return res;
}
int main()
{
int T;
cin>>T;
while(T--)
{
ll a,p,phi=0,n;
scanf("%lld%lld",&a,&p);
phi=n=p;
for(ll i=2;i<=sqrt(n)+1;i++)
{
if(!(n%i))
{
phi=phi*(i-1)/i;
while(!(n%i)) n/=i;
}
}
if(n>1) phi=phi*(n-1)/n,n=1;
ll res=phi;bool fo=0;
for(ll i=1;i*i<=phi;i++)
{
if(!(phi%i))
if(ksm(a,i,p)==1)
{
res=i;break;
}
else if(ksm(a,phi/i,p)==1) res=phi/i; //这里取了个巧,同时算大和小的因数
}
if(!fo) printf("%lld\n",res);
}
return 0;
}
欧拉定理
若 \(\gcd(a,p)=1\),则
$$
a^c\equiv a^{c\bmod \varphi(p)}\pmod p
$$
或者有另外一种等价的形式
当 \(p\) 为质数时,显然有 \(a^{p-1}=1\),即费马小定理。也就是说费马小定理是欧拉定理的特殊情况。
扩展欧拉定理
这个主要是对超高次幂的处理。
欧拉反演(?)
由
有
这当然只是一种特殊的形式,但是这个东西在处理和式的时候很常用。因为这个欧拉函数的 \(\sum\) 大概率可以提到前面去。
P2398 GCD SUM
求
转化一下就好了。
然后就可以暴力了。
code
这里没有用线性筛去筛欧拉函数,而是暴力,因为能过。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
long long phi[100005];
long long ans=0;
void getphi(int n)
{
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-1)/i;
}
int main()
{
long long n;cin>>n;
getphi(n);
for(int i=1;i<=n;i++)
{
ans+=1ll*phi[i]*(n/i)*(n/i);
}
cout<<ans<<endl;
}
莫比乌斯函数及其反演
莫比乌斯函数
很好理解 \(\mu\) 是积性函数。但是这个定义略显奇怪。这是因为它满足如下式子:
也就是说有
事实上莫比乌斯函数是为了满足上面的那个式子才被定义出来的。
类似的有
事实上只是将 \(n\) 换成了 \(\gcd(i,j)\),但是在很多式子中会很有用。
莫比乌斯反演
但是事实上这个式子并不是很常用,限制有点多。
更常用的是上面的式子和另一个,把它们合起来写:
P1829 [国家集训队] Crash的数字表格 / JZPTAB
求
显然先将 \(\operatorname{lcm}\) 拆开,拆成 \(\gcd\) 后套路性的枚举 \(\gcd\),类似交换和式处理使得出现整除分块的形式:
我们把 \(\sum_{i=1}^{x}\sum_{j=1}^{y}[\gcd(i,j)=1]ij\) 设为 \(solve1(x,y)\),那上面的式子就是
显然可以整除分块。然后再来考虑 \(solve1\) 怎么处理。
用上面的式子把 \([\gcd(i,j)=1]\) 拆成 \(\sum_{d|\gcd(i,j)}\mu(d)\),仍然是交换和式变成可整除分块的形式:
这时最后两个求和符号显然都可以 \(O(1)\) 计算。记 \(g(n,m)=\sum_{i=1}^n i \sum_{j=1}^n j=\frac{i(i+1)}{2}\frac{j(j+1)}{2}\),
这样 \(solve1\) 也可以整除分块。两个整除分块的嵌套听大佬说时间复杂度是 \(O(n)\) 的。(不太会证)
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e7+7;
const int p=20101009;
int n,m,mu[N],zhi[N],sum[N],ni;
bool sign[N];
int ksm(int x,int k){int res=1;while(k){if(k&1)res=res*x%p;x=x*x%p,k>>=1;}return res;}
void init()
{
mu[1]=1;int cnt=0,up=min(n,m);sign[0]=sign[1]=1;ni=ksm(2,p-2);
for(int i=2;i<=up;i++){
if(!sign[i]) zhi[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&i*zhi[j]<=up;j++){
sign[i*zhi[j]]=1;
if(i%zhi[j]==0) {mu[i*zhi[j]]=0;break;}
mu[i*zhi[j]]=-mu[i];
}
}
for(int i=1;i<=up;i++) sum[i]=(sum[i-1]+i*i%p*mu[i]%p+p)%p;
}
int g(int x,int y){return x*(x+1)%p*y%p*(y+1)%p*ni%p*ni%p;}
int solve2(int x,int y)
{
int res=0;
for(int l=1,r;l<=min(x,y);l=r+1)
{
r=min(x/(x/l),y/(y/l));
res=(res+(sum[r]-sum[l-1])%p*g(x/l,y/l)%p+p)%p;
}
return res;
}
int solve1()
{
int res=0;
for(int l=1,r;l<=min(n,m);l=r+1)
{
r=min(n/(n/l),m/(m/l));
res=(res+(l+r)*(r-l+1)%p*ni%p*solve2(n/l,m/l)%p+p)%p;
}
return res;
}
signed main()
{
cin>>n>>m;init();
cout<<solve1()<<'\n';return 0;
}
中国剩余定理
对于两两互质的 \(n_i\),求
- 计算所有模数的积 \(n\)。
- 对于每一个 \(i\),
计算 \(m_i=\frac{n}{n_i}\)。
计算 \(\mod n_i\) 意义下的 \(m_i\) 的逆元 \(m_i^{-1}\)。 - \(x=\sum_{i=1}^k a_im_im_i^{-1} \mod n\)。
正确性大概是只有对应的模数的时候才不为 0。
P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪
模板题。通过exgcd来求逆元即可。就是不知道为什么本地运行就是错的在在线IDE上就是对的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int __int128
#define ll long long
const long N=15;
int q,a[N],b[N],ans=0,n=1;
void exgcd(int a,int b,int &x,int &y){
if(b==0){x=1,y=0;return;}
int x1,y1;exgcd(b,a%b,x1,y1);
x=y1,y=x1-(a/b)*y1;
}
signed main(){
scanf("%lld",&q);
for(int i=1;i<=q;i++){scanf("%lld %lld",&a[i],&b[i]);n*=a[i];}
for(int i=1;i<=q;i++){
int m=n/a[i],x,y;exgcd(m,a[i],x,y);
ans=(ans+m*x*b[i]%n+n)%n;
}
printf("%lld",ans);
return 0;
}
P2480 [SDOI2010] 古代猪文
求
一道比较综合的题目。
考虑用欧拉定理的第一种形式来化简,将取模移到指数上。注意这里999911659是一个质数。
显然999911658是一个合数,有 \(999911658=2\times 3\times 4679\times 35617\)。
发现这些因子的次数都是1,因此可以用普通的CRT拆分后求解。
我们设 \(\sum_{k|n}{n\choose k}\mod 999911658=t\),那么
因此我们将问题转化为求 \(ans_i\)。对于这某一种模数,我们可以暴力地去枚举 \(k\) 使得 \(k|n\) 然后用lucas去算模上面四个模数意义下的 \(n\choose k\)。
最后用CRT合并起来即可。
code
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,g,a[10]={0,2,3,4679,35617},ans[10];
const int p=999911658;
int ksm(int x,int k,int p1){
int res=1;
while(k){
if(k&1) res=res*x%p1;
x=x*x%p1;k>>=1;
}
return res;
}
int C(int x,int y,int p1){
int res1=1,res2=1;
for(int i=x;i>=x-y+1;i--) res1=res1*i%p1;
for(int i=y;i>=1;i--) res2=res2*i%p1;
return ksm(res2,p1-2,p1)*res1%p1;
}
int lucas(int x,int y,int p1){
if(y==0) return 1;
return (C(x%p1,y%p1,p1)*lucas(x/p1,y/p1,p1))%p1;
}
int crt(){
int res=0;
for(int i=1;i<=4;i++){
int m=p/a[i],m1=ksm(m,a[i]-2,a[i]);
res=(res+ans[i]*m%p*m1%p)%p;
}
return res;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>g;
if(g%(p+1)==0){cout<<'0';return 0;}
for(int i=1;i<=4;i++){
for(int j=1;j*j<=n;j++){
if(n%j==0) {
ans[i]=(ans[i]+lucas(n,j,a[i]))%a[i];
if(j*j!=n) ans[i]=(ans[i]+lucas(n,n/j,a[i]))%a[i];
}
}
}
int res=crt();
cout<<ksm(g,res,p+1);
return 0;
}
升幂定理
好难啊,咕咕咕。
阶乘取模
定义 \(\nu _p(n)\) 表示 \(n\) 的标准分解中 \(p\) 这个质因子的次数,即 \(p^{\nu _p(n)}|n\),\(p^{\nu _p(n)+1}\nmid n\)。
Wilson 定理
对于 \(1\le n\),
lucas定理
对于质数 \(p\),有
引理1
引理2
P3807 【模板】卢卡斯定理/Lucas 定理
直接应用上面的。\(n、m\)较小时就可以直接暴力求。由于每个 \(p\) 不同,因此不能预处理阶乘。
又因为 \(p\) 可能比 \(n、m\) 小,因此不能递推求逆元。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int p,cnt;
int ksm(int x,int k)
{
int res=1;
while(k)
{
if(k&1) res=res*x%p;
k>>=1;x=x*x%p;
}
return res;
}
int C(int n,int m)
{
int jie1=1,jie2=1;
for(int i=n;i>=n-m+1;i--) jie1=jie1*i%p;
for(int i=1;i<=m;i++) jie2=jie2*i%p;
jie2=ksm(jie2,p-2);return jie1*jie2%p;
}
int lucas(int n,int m)
{
if(m==0) return 1;
return (C(n%p,m%p)*lucas(n/p,m/p))%p;
}
void solve()
{
int n,m;cin>>n>>m>>p;n=n+m;cnt=0;
cout<<lucas(n,m)<<'\n';
}
signed main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--) solve();
}
同余方程
好难啊,咕咕咕。
二次剩余
对于整数 \(a,p\) 且 \(a,p\) 互质,若存在整数 \(x\) 使得
则称 \(a\) 为模 \(p\) 的二次剩余。一般而言,我们都只讨论 \(p\) 为奇素数的情况。
可以发现,二次剩余是一个类似于模意义下开根号的运算。
Euler判定法
所谓判定法就是判定一个数 \(a\) 是否是模数 \(p\) 的二次剩余。
Legendre 符号
定义Legendre 符号形如 \({\left({a\over p} \right )}\),其中 \(a\) 为整数 \(p\) 为奇素数。其值如下:
可以发现这个值几乎就是Euler判定法的具体表示。
性质
- 同余
- 类似于完全积性的性质
Cipolla 算法
P5491 【模板】二次剩余
解 $x^2 \equiv n \pmod{p} $。
找一个 \(x=a\) 使得 \(a^2-n\) 不是一个二次剩余。这里可以直接随机找一个用Euler判定是不是就可以了。因为是二次剩余的值有 \(\frac{p-1}{2}\) 个。随机的话复杂度就是对的。
然后定义 $i^2 \equiv a^2-n \pmod{p} $。
由于 \(a^2-n\) 不是二次剩余,因此实际上 \(i\) 与虚数是类似的,然后我们就可以定义类似的运算。
定理:
- $i^p \equiv -i \pmod{p} $
- $(x+y)^p \equiv x^p+ y^p \pmod{p} $
对于定理1,有
由于Euler判定,因为 \(a^2-n\) 不是 \(p\) 的二次剩余,因此
综上,定理1得证。
综合定理1和2,那么有
证明的话将左边展开后根据上面两个定理即可。
那么 \((a+i)^{\frac{p+1}{2}}\) 就是解。可以用反证法证明最终结果一定不含 \(i\),因此照着这个做即可。
code
注意要写一个类似于虚数乘法的东西。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
mt19937 rnd(time(0));
int n,p,i_2;
struct node{int real,image;};
int simple_ksm(int x,int k){
int res=1;
while(k){
if(k&1) res=res*x%p;
x=x*x%p,k>>=1;
}
return res%p;
}
node operator * (const node& x,const node& y){
node res;
res.real=(x.real*y.real%p+i_2*x.image%p*y.image%p)%p;
res.image=(x.real*y.image%p+x.image*y.real%p)%p;
return res;
}
node complex_ksm(node x,int k){
node res=x;k--;
while(k){
if(k&1) res=res*x,res.image%=p,res.real%=p;
x=x*x,x.image%=p,x.real%=p;k>>=1;
}
return res;
}
bool Euler_determination(int x){return (simple_ksm(x,(p-1)/2)==1);}
void solve(){
cin>>n>>p;n%=p;int a=rnd()%p;
if(n==0) {cout<<"0\n";return;}
if(Euler_determination(n)==0) {cout<<"Hola!\n";return;}
while(Euler_determination((a*a%p-n+p)%p)==1) a=rnd()%p;// cout<<"** "<<a<<'\n',
i_2=(a*a%p-n+p)%p;
int res1=complex_ksm((node){a,1},(p+1)/2).real%p,res2=(p-res1)%p;
if(res1>res2) swap(res1,res2);
cout<<res1<<' '<<res2<<'\n';
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--) solve();
return 0;
}

浙公网安备 33010602011771号