数论基础
        
          数论
      
        
          线性筛素数
      洛谷P3383 【模板】线性筛素数
数据范围:n=10^8,找出第k 小的质数
欧拉线性筛的关键在于:每个合数只被它最大的非自身的因数筛掉:最大的非自身的因数*最小质因子=这个数
例如:12不会被4筛掉,而是被6筛掉;45不会被9筛掉,而是被15筛掉。
i的最小质因子一定是prime[j],因为如果小于j,一定会在更早被枚举到时一定会break掉,就不存在现在这一步
i*prime[j]的最小质因子也是prime[j],与上一步同理
(对break情况的进一步说明)当出现i的最小质因子prime[j]时,
      我们就知道n*prime[j](n>i) 的形式一定会在后面再次出现,也就是说这个数的最大因数还没有出现,我们留到后面再把他筛掉,及时break掉保证每个数只被筛一次
      通俗易懂的举例:以12为例,2,3,4,6都可筛掉12
      i=2:2 * 2=4 break; i=3 3 * 2=6, 3 * 3=9 break;
      i=4: 4 * 2 =8 break; i=5 5 * 2=10, 5 * 3=15; 5 * 5=25 break;
      i=6: 6 * 2=12 break;实现了在12的最大因数6 才将12筛掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | #include<iostream> #include<cstdio> using namespace std; const int N=1e8+5; int a[N],prime[N/3]; int cnt=0; int n,q,op; void init() {     for(int i=2;i<=n;i++)     {         if(!a[i])prime[++cnt]=i;          for(int j=1;i*prime[j]<=n and j<=cnt;j++)         {             a[i*prime[j]]=1;             if(i%prime[j]==0)break;                      }     } } int main() {     ios::sync_with_stdio(0);     cin>>n>>q;     init();     while(q--)     {         cin>>op;         cout<<prime[op]<<endl;     }     return 0; }
   | 
 
        
          求约数个数
      
acwing 870
给定n个正整数ai,请你输出这些数的乘积的约数个数,答案对10^9+7取模。
        
          输入格式
      第一行包含整数n。
接下来n行,每行包含一个整数ai。
        
          输出格式
      输出一个整数,表示所给正整数的乘积的约数个数,答案需对10^9+7取模。
        
          数据范围
      1≤n≤100
1≤ai≤2∗10^9
        
          输入样例:
      
        
          输出样例:
      
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
   | #include<iostream> #include<cstdio> #include<cmath> #include<unordered_map> using namespace std; const int m=1e9+7; int n,x; int a[10050]; int cnt=0; int main() {     cin>>n;     unordered_map<int,int> prime;     while(n--)     {         cin>>x;         for(int i=2;i<=x/i;i++)         {             while(x%i==0)             {                 x/=i;                 prime[i]++;             }         }         if(x>1)prime[x]++;     }     long long res=1;     for(auto i:prime)     {         res=res*(i.second+1)%m;     }     cout<<res;     return 0; }
   | 
 
        
          求约数之和
      
acwing 871
给定n个正整数ai,请你输出这些数的乘积的约数之和,答案对10^9+7取模。
        
          输入格式
      第一行包含整数n。
接下来n行,每行包含一个整数ai。
        
          输出格式
      输出一个整数,表示所给正整数的乘积的约数之和,答案需对10^9+7取模。
        
          数据范围
      1≤n≤100,
1≤ai≤2∗10^9
        
          输入样例:
      
        
          输出样例:
      
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   | #include<iostream> #include<cstdio> #include<cmath> #include<unordered_map> using namespace std; const int m=1e9+7; int n,x; int a[10050]; int cnt=0; int main() {     cin>>n;     unordered_map<int,int> prime;     while(n--)     {         cin>>x;         for(int i=2;i<=x/i;i++)         {             while(x%i==0)             {                 x/=i;                 prime[i]++;             }         }         if(x>1)prime[x]++;     }     long long res=1;     for(auto i:prime)     {         int p=i.first,a=i.second;         long long t=1;         while(a--)         {             t=(t*p+1)%m;         }         res=res*t%m;     }     cout<<res;     return 0; }
   | 
 
        
          最大公约数
      欧几里得算法:辗转相除法 
d|a  and  d|b  —->   d|ax+by
gcd(a,b)=gcd(b,a-c*b)=gcd(b,a%b);
代码:
1 2 3 4
   | int gcd(int a,int b) {     return b==0?a:gcd(b,a%b); }
   | 
 
        
          欧拉函数
      
        
          定义
      在数论,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目(因此φ(1)=1)
        
          算数基本定理
      算术基本定理又叫唯一分解定理,可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积N=P1^a1^ P2^a2^P3^a3^……Pn^an^,这里P1<P2<P3……<Pn均为质数,其中指数ai是正整数。

        
          acwing873 欧拉函数
      给定n个正整数ai,请你求出每个数的欧拉函数。
1≤n≤100,
1≤ai≤2∗10^9
输入样例:
输出样例:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | #include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n; int main() {     cin>>n;     while(n--)     {         int x;         cin>>x;         int res=x;         for(int i=2;i<=x/i;i++)         {             if(x%i==0)             {                 res=res/i*(i-1);                 while(x%i==0)x/=i;             }         }         if(x>1)res=res/x*(x-1);         cout<<res<<endl;     }     return 0;  }
   | 
 
        
          acwing874 筛法求欧拉函数
      给定一个正整数n,求1~n中每个数的欧拉函数之和。1≤n≤10^6
解析:欧拉函数的筛法是线性筛素数的加强版
第一种解释:
要想求欧拉函数需要用到以下几个性质( p为素数 ):
phi(p) == p-1 因为素数p除了1以外的因子只有p,所以与 p 互素的个数是 p - 1个
 
phi(p^k) == p^k - p^(k-1) == (p-1) * p^(k-1)
证明:
令n == p^k,小于 n 的正整数共有 p^k-1 个,其中与 p 不互素的个数共 p^(k-1)-1 个,它们是 1p,2p,3*p … (p^(k-1)-1)*p
所以phi(p^k) == (p^k-1) - (p^(k-1)-1) == p^k - p^(k-1) == (p-1) * p^(k-1)。
 
如果i mod p == 0, 那么 phi(i * p) == p * phi(i) (证明略)
举个例子:
假设 p = 3,i = 6,p * i = 18 = 2 * 3^2;
phi(3 * 6) == 18*(1-1/2)*(1-1/3) = 6
p * phi(i) = 3 * phi(6) = 3 * 6 * (1-1/2) *  (1-1/3) = 6 = phi(i * p) 正确
 
如果i mod p != 0, 那么 phi(i * p) == phi(i) * (p-1) 
证明:
i mod p 不为0且p为质数, 所以i与p互质, 那么根据积性函数的性质 phi(i * p) == phi(i) * phi(p) 其中phi(p) == p-1
所以 phi(i * p) == phi(i) * (p-1).
再举个例子:
假设i = 4, p = 3, i * p = 3 * 4 = 12
phi(12) = 12 * (1-1/2) * (1-1/3) = 4
phi(i) * (p-1) = phi(4) * (3-1) = 4 * (1-1/2) * 2 = 4 = phi(i * p)正确
 
第二种解释:
phi[prime[j] * i]分为两种情况:
- i % prime[j] == 0时:primes[j]是i的最小质因子,也是prime[j] * i的最小质因子,因此1 - 1 / prime[j]这一项在phi[i]中计算过了,只需将基数N修正为prime[j]倍,最终结果为phi[i] * prime[j]。
 
- i % primes[j] != 0时:primes[j]不是i的质因子,只是prime[j] * i的最小质因子,因此不仅需要将基数N修正为prime[j]倍,还需要补上1 - 1 / prime[j]这一项,因此最终结果phi[i] * (prime[j] - 1)。
 
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | #include<iostream> #include<cstdio> using namespace std; const int N=1e6+5; int a[N],prime[N/3]; int phi[N]; long long tot=0,cnt=0; int n,q,op; void init() {     phi[1]=1;     for(int i=2;i<=n;i++)     {         if(!a[i])         {             prime[++cnt]=i;              phi[i]=i-1;                      }         for(int j=1;i*prime[j]<=n and j<=cnt;j++)         {             a[i*prime[j]]=1;             if(i%prime[j]==0)             {                 phi[prime[j]*i]=phi[i]*prime[j];                                  break;             }             phi[prime[j]*i]=phi[i]*(prime[j]-1);                      }     }     for(int i=1;i<=n;i++)     {         tot+=phi[i];     } } int main() {     ios::sync_with_stdio(0);     cin>>n;     init();     cout<<tot;     return 0; }
   | 
 
        
          欧拉定理
      内容:
    若a与n互质,则有a^phi(n) ≡ 1(mod p)。
证明:

费马小定理(欧拉定理的特例):
如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)。
        
          快速幂
      基于 二进制 位运算 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
   | #include<iostream> #include<cstdio> using namespace std; #define ll long long  ll qpow(ll a,ll b,ll p) {     ll res=1;     while(b)     {         if(b&1)         {             res=res*a%p;         }         a=a*a%p;         b>>=1;     }     return res%p; } int main() {     ios::sync_with_stdio(0);     int n;     ll a,b,p;     cin>>n;     while(n--)     {         cin>>a>>b>>p;         cout<<qpow(a,b,p)<<endl;     }     return 0; }
   | 
 
        
          乘法逆元
      参考:https://www.cnblogs.com/zjp-shadow/p/7773566.html
        
          逆元的定义
      

        
          求法总结
      
- 拓展欧几里得:解线性同余方程,不要求模数为质数
 
- 快速幂:费马小定理,要求模数为质数
 
- 线性求逆元,求一连串数字对一个质数p的逆元,线性递推,要求模数为质数
 
        
          快速幂求逆元
      原理:

代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | #include<iostream> #include<cstdio> using namespace std; #define ll long long  ll qpow(ll a,ll b,ll p) {     ll res=1;     while(b)     {         if(b&1)         {             res=res*a%p;         }         a=a*a%p;         b>>=1;     }     return res%p; } int gcd(int a,int b) {     return b==0?a:gcd(b,a%b); } int main() {     ios::sync_with_stdio(0);     int n;     ll a,b,p;     cin>>n;     while(n--)     {         cin>>a>>p;         ll res=qpow(a,p-2,p);         if(gcd(a,p)==1)cout<<res<<endl;         else cout<<"impossible"<<endl;              }     return 0; }
   | 
 
        
          扩展欧几里得算法
      引理:裴蜀定理
若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。
它的一个重要推论是:a,b互质的充分必要条件是存在整数x,y使ax+by=1
一些自己的理解:
a*x mod n==1   <=>   (ax-1)整除n   <=>   令y=-(ax-1)/n 
<=>  ax-ny==1 目标转化为求方程的解x,y(x的解为x0+kn)
求方程的解:ax+by=c,c一定是gcd(a,b)的倍数,否则无整数解 (裴蜀定理)
ax+by=gcd(a,b)=1   –>  联想到gcd(a,b)的求法:gcd(a,b)=gcd(b,a%b); (1式)
有:bx1+(a%b)y1=gcd(b,a%b)=1 (2式)
由 1式==2式       –>  ax+by=bx1+(a-(a/b))y1  
                            –>  ax+by=ay1+b(x1-(a/b)y1)      
                            –>x=y1,y=x1-(a/b)y1 得到递推关系,可以设计递归函数
求x,y转化为求x1,y1   
——why?   ——由gcd(a,b)的边界条件为 b=0 可知递归边界一定是 ax+0y==gcd(a,0), 随便给一组合法解为x=1,y=0
从上往下推:求gcd ; 从下往上推:求最初二元一次不定方程的解!
扩欧求乘法逆元代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | #include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #define ll long long using namespace std; ll x,y; void exgcd(ll a,ll b) {     if(b==0)     {         x=1,y=0;         return ;     }     exgcd(b,a%b);     ll x0=x,y0=y;     x=y0;     y=x0-(a/b)*y0;           return ; } int main() {     ll a,n;     while(cin>>a>>n)     {         if(a==0 and n==0)break;         exgcd(a,n);         while(x<0)         {             x+=n;         }         x%=n;                  cout<<x<<endl;     }     return 0; }
   | 
 
acwing877 扩展欧几里得算法
给定n对正整数ai,bi,对于每对数,求出一组xi,yi,使其满足ai∗xi+bi∗yi=gcd(ai,bi)。本题答案不唯一,输出任意满足条件的xi,yi均可。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | #include<iostream> #include<cstdio> using namespace std; #define ll long long int n; ll x,y; void exgcd(ll a,ll b) {     if(b==0)     {         x=1,y=0;         return ;     }     exgcd(b,a%b);     ll x1=x,y1=y;     x=y1;     y=x1-y1*(a/b);     return ; } int main() {     ios::sync_with_stdio(0);     cin>>n;     ll a,b;     while(n--)     {         cin>>a>>b;         exgcd(a,b);         cout<<x<<" "<<y<<endl;     }     return 0; }
   | 
 
acwing878 线性同余方程
给定n组数据ai,bi,mi,对于每组数求出一个xi,使其满足ai∗xi≡bi(mod mi),如果无解则输出impossible。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | #include<iostream> #include<cstdio> using namespace std; #define ll long long int n; ll x,y; ll d; void exgcd(ll a,ll b) {     if(b==0)     {         x=1,y=0;         return ;     }     exgcd(b,a%b);     ll x1=x,y1=y;     x=y1;     y=x1-y1*(a/b);     return ; } ll gcd(ll a,ll b) {     return b==0?a:gcd(b,a%b); } int main() {     ios::sync_with_stdio(0);     cin>>n;     ll a,b,m;     while(n--)     {         cin>>a>>b>>m;         d=gcd(a,m);         if(b%d==0)         {             exgcd(a,m);             cout<<x*(b/d)%m<<endl;         }         else cout<<"impossible"<<endl;     }     return 0; }
   | 
 
        
          中国剩余定理
      内容
