[关闭]
@dxbdly 2021-03-03T14:46:24.000000Z 字数 11109 阅读 211

G2020 19寒假集训Day4

信息学——19寒假集训


今天是G2020寒假集训Day4了,数学专题终于来了,主讲数论。

1.最小公倍数与最大公因数

定义

定义如下:

  1. 最大公因数(gcd):多个数的公共因数中最大的数
  2. 最小公倍数(lcm):多个数的公共倍数中最小的数

最小公倍数的性质

性质1:

我们一般用来表示

性质1推论:

的表示方法:

设:的集合。

最大公因数的性质

性质1:

性质2:

2.欧几里得与拓展欧几里得

欧几里得算法

欧几里得算法是求的一种方法,其核心为:

根据此式不断递归,缩小,此算法又称辗转相除法。

拓展欧几里得算法

拓展欧几里得适用问题:

给定求一组满足的解

拓展欧几里得的内容:

若:有满足:

则有一组特殊解

证明如下:

满足:

整理得:

对比方程:

所以有:

所以易得:

拓展欧几里得求整数解系

我们之前已经求出了一组特殊整数解。

但是如果要我们求出所有解怎么办呢?

我们可以考虑不定方程求解系的做法。

由于我们已经得到了一组特殊解,则整个解系为:


这是不定方程的基本知识,不再赘述。

拓展欧几里得解不定方程

拓展欧几里得多用于解,形如的不定方程

我们首先要知道,此方程有整数解的充分条件为:

那么我们可以考虑先求出的解。

直接使用拓展欧几里得定理即可。

那么只需要将分别乘上

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f=f|(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. inline int get_gcd(int a,int b)
  16. {
  17. return !b?a:get_gcd(b,a%b);
  18. }
  19. inline void Exgcd(int a,int b,int &x,int &y)
  20. {
  21. if (!b)
  22. x=1,y=0;
  23. else
  24. Exgcd(b,a%b,y,x),y-=a/b*x;
  25. }
  26. int main(){
  27. int a,b,c,x,y;
  28. a=read(),b=read(),c=read();
  29. Exgcd(a,b,x,y);
  30. printf("%d %d",x*c/get_gcd(a,b),y*c/get_gcd(a,b));
  31. return 0;
  32. }

3. 乘法逆元

逆元定义

,则称意义下的逆元,记为,也可以说意义下的倒数。

大多数求逆元的方法都要求为质数。

求解逆元的方式有很多,下面给出4种求法。

拓展欧几里得求逆元

注意此算法只要求不一定为质数。

使用拓展欧几里得求解逆元是很好的选择,他的速度比其他算法稍快,并且适用性比较广。

我们可以将同余方程变为不定方程

求解这个方程的解直接套用拓展欧几里得就可以了。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f=f|(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. inline void Exgcd(int a,int b,int &x,int &y)
  16. {
  17. if (!b)
  18. x=0,y=1;
  19. else
  20. Exgcd(b,a%b,x,y),y-=a/b*x;
  21. }
  22. int main(){
  23. int a,b,x,y;
  24. a=read(),b=read();
  25. Exgcd(a,b,x,y);
  26. x=(x%b+b)%b;
  27. printf("%d",x);
  28. return 0;
  29. }

费马小定理求逆元

这个算法需要用到费马小定理:

,其中,且为质数

(后面的章节有详细讲解)

可以发现此同余式右边正好为,且都是意义下的。

考虑将原式的替换,则:

则进一步有:

所以我们只需要用快速幂求出即可。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f=f|(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. inline int ksm(int a,int b,int p)
  16. {
  17. int res=1;
  18. while (b)
  19. {
  20. if (b&1)
  21. res=res*a%p;
  22. a=a*a%p,b>>=1;
  23. }
  24. return res%p;
  25. }
  26. int main(){
  27. int a,b;
  28. a=read(),b=read();
  29. printf("%d",ksm(a,b-2,b))
  30. return 0;
  31. }

求多个连续逆元

题面

考虑如何线性求意义下的逆元。

首先要知道,

我们设,则

把这个式子放到意义下就可以得到:

将其乘上
可得:

代入得:

就是的逆元,所以可以线性求出。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read()
  6. {
  7. int x=0;
  8. char c=getchar();
  9. bool f=0;
  10. while (!isdigit(c))
  11. f=f|(c=='-'),c=getchar();
  12. while (isdigit(c))
  13. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  14. return f?-x:x;
  15. }
  16. int n,p;
  17. int inv[3000005];
  18. signed main(){
  19. n=read(),p=read();
  20. inv[1]=1;
  21. for (register int i=2;i<=n;++i)
  22. inv[i]=(p-p/i)*inv[p%i]%p;
  23. for (register int i=1;i<=n;++i)
  24. printf("%lld\n",inv[i]);
  25. return 0;
  26. }

4.整除分块

内容

经典问题:

的值

对于此题,我们可以先考虑在不同情况下的值

不难发现,最多只有种取值。

可以考虑:将原式根据的取值,分成几段求解。

均相等,则很好计算。

,且计算的时间复杂度在

所以我们可以的枚举块,对于每一个块求解,总时间复杂度

代码如下:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read()
  6. {
  7. int x=0;
  8. char c=getchar();
  9. bool f=0;
  10. while (!isdigit(c))
  11. f=f|(c=='-'),c=getchar();
  12. while (isdigit(c))
  13. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  14. return f?-x:x;
  15. }
  16. int n,ans;
  17. signed main(){
  18. n=read();
  19. for (register int l=1,r;l<=n;l=r+1)
  20. r=n/(n/l),ans+=(r-l+1)*(n/l);
  21. printf("%lld",ans);
  22. return 0;
  23. }

例题P2261 [CQOI2007]余数求和

题面

题解:

分析数据范围,显然不能暴力,们先来推式子

由题意:

运算转化为整除运算:

整理得

那么我们可以利用整除分块解决。

那么对于
使得相等的公式为

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read()
  6. {
  7. int x=0;
  8. char c=getchar();
  9. bool f=0;
  10. while (!isdigit(c))
  11. f|=(c=='-'),c=getchar();
  12. while (isdigit(c))
  13. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  14. return f?-x:x;
  15. }
  16. int n,k;
  17. int sum;
  18. signed main(){
  19. n=read(),k=read();
  20. for (register int l=1,r;l<=n;l=r+1)
  21. {
  22. int x=k/l;
  23. r=(x?min(k/x,n):n);
  24. sum+=(r-l+1)*(l+r)/2*x;
  25. }
  26. printf("%lld",n*k-sum);
  27. return 0;
  28. }

5. 函数

定义:

的公式

其中的质因数。

可以理解为:

即统计,~中与互质的个数

是一个积性函数

的求法需要用到线性筛,下一点中会提到。

6.筛法

埃拉托斯特尼筛法(埃氏筛)

基本算法,不多讲,很简单。

其思想是每次确定一个质数,就将此质数所有倍数排除。

时间复杂度约

欧拉筛(线性筛)

欧拉筛其实就是在埃氏筛上做出了优化,使得时间复杂度成功降到

考虑埃氏筛的冗余,一个数可能会被多个质数所筛中,从而增大复杂度。

欧拉筛法保证了每个数都只被一个质数筛一遍,算法结束时每个数都要被筛到,所以复杂度为

先放代码:

  1. //The code is from dxbdly
  2. inline void get_prime()
  3. {
  4. for (register int i=2;i<=n;++i)
  5. {
  6. if (!b[i])
  7. prime[++tot]=i;
  8. int x=prime[1]*i;
  9. for (register int j=1;j<=tot&&x<=n;++j,x=prime[j]*i)
  10. {
  11. b[x]=1;
  12. if (!(i%prime[j]))
  13. break;
  14. }
  15. }
  16. }

注意到最后一个语句,那便是防止一个数重复被筛的关键。

欧拉筛求~

我们容易得出:

那么即可在线性筛时的处理出~的值

代码:

  1. //The code is from dxbdly
  2. inline void get_phi()
  3. {
  4. phi[1]=1;
  5. for (register int i=2;i<=n;++i)
  6. {
  7. if (!b[i])
  8. prime[++tot]=i,phi[i]=i-1;
  9. int x=prime[1]*i;
  10. for (register int j=1;j<=tot&&x<=n;++j,x=prime[j]*i)
  11. {
  12. b[x]=1;
  13. if (!(i%prime[j]))
  14. {
  15. phi[x]=prime[j]*phi[i];
  16. break;
  17. }
  18. phi[x]=(prime[j]-1)*phi[i];
  19. }
  20. }
  21. }

7.费马小定理,欧拉定理,扩展欧拉定理

内容

费马小定理:

是一个质数, 且 不是 的倍数,则有:

欧拉定理:

互质,则有:

拓展欧拉定理:

,则有 :

例题P4139 上帝与集合的正确用法

题面

题解:

我们令:

则,套用拓展欧拉定理可得

预处理出~,递归求解

递归复杂度,预处理复杂度

总复杂度:可过!

AC代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f|=(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int t,p;
  16. int prime[10000005],phi[10000005],tot;
  17. bool b[10000005];
  18. inline void get_phi()
  19. {
  20. phi[1]=1;
  21. for (register int i=2;i<=10000000;++i)
  22. {
  23. if (!b[i])
  24. prime[++tot]=i,phi[i]=i-1;
  25. int x=prime[1]*i;
  26. for (register int j=1;j<=tot&&x<=10000000;++j,x=prime[j]*i)
  27. {
  28. b[x]=1;
  29. if (!(i%prime[j]))
  30. {
  31. phi[x]=prime[j]*phi[i];
  32. break;
  33. }
  34. phi[x]=(prime[j]-1)*phi[i];
  35. }
  36. }
  37. }
  38. inline int ksm(int a,int b,int m)
  39. {
  40. int ans=1;
  41. while (b)
  42. {
  43. if (b&1)
  44. ans=1ll*ans*a%m;
  45. a=1ll*a*a%m,b>>=1;
  46. }
  47. return ans;
  48. }
  49. inline int get_ans(int x)
  50. {
  51. if (x<=2)
  52. return 0;
  53. return ksm(2,get_ans(phi[x])+phi[x],x);
  54. }
  55. int main(){
  56. t=read();
  57. get_phi();
  58. while (t--)
  59. {
  60. p=read();
  61. printf("%d\n",get_ans(p));
  62. }
  63. return 0;
  64. }

8. 中国剩余定理与拓展中国剩余定理

注:部分引用自博客

中国剩余定理(CRT)

问题:

求同余方程组:

其中为两两互质的整数。

的最小非负整数解。

令:

令:为同余方程

则,一定有一个解

且,其最小非负整数解为:

代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f=f|(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n;
  16. int a[100005],b[100005],x,y;
  17. inline void exgcd(int a,int b)
  18. {
  19. if (b==0)
  20. {
  21. x=1,y=0;
  22. return ;
  23. }
  24. exgcd(b,a%b);
  25. int x0=x,y0=y;
  26. x=y0,y=x0-a/b*y0;
  27. }
  28. inline void work()
  29. {
  30. int lcm=1,ans=0;
  31. for (register int i=1;i<=n;++i)
  32. lcm*=b[i];
  33. for (register int i=1;i<=n;++i)
  34. {
  35. int cnt=lcm/b[i];
  36. exgcd(cnt,b[i]);
  37. x=(x%b[i]+b[i])%b[i];
  38. ans=(ans+a[i]*x*cnt)%lcm;
  39. }
  40. printf("%d",(ans%lcm+lcm)%lcm);
  41. }
  42. int main(){
  43. n=read();
  44. for (register int i=1;i<=n;++i)
  45. a[i]=read();
  46. for (register int i=1;i<=n;++i)
  47. b[i]=read();
  48. work();
  49. return 0;
  50. }

例题P3868 [TJOI2009]猜数字

题面

题解:

由题意得:

化为同余式:

整理:

直接套用中国剩余定理即可。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read()
  6. {
  7. int x=0;
  8. char c=getchar();
  9. bool f=0;
  10. while (!isdigit(c))
  11. f=f|(c=='-'),c=getchar();
  12. while (isdigit(c))
  13. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  14. return f?-x:x;
  15. }
  16. int n;
  17. int a[15],b[15];
  18. int x,y;
  19. inline int mul(int a,int b,int m)//此题需要用快速乘法加速
  20. {
  21. int ans=0;
  22. while (b)
  23. {
  24. if (b&1)
  25. ans=(ans+a)%m;
  26. a=(a<<1)%m,b>>=1;
  27. }
  28. return ans;
  29. }
  30. inline void exgcd(int a,int b)
  31. {
  32. if (b==0)
  33. {
  34. x=1,y=0;
  35. return ;
  36. }
  37. exgcd(b,a%b);
  38. int x0=x,y0=y;
  39. x=y0,y=x0-a/b*y0;
  40. }
  41. inline int work()
  42. {
  43. int lcm=1,ans=0;
  44. for (register int i=1;i<=n;++i)
  45. lcm*=b[i];
  46. for (register int i=1;i<=n;++i)
  47. {
  48. int cnt=lcm/b[i];
  49. exgcd(cnt,b[i]);
  50. x=(x%b[i]+b[i])%b[i];
  51. ans=(ans+mul(mul(a[i],x,lcm),cnt,lcm))%lcm;
  52. }
  53. printf("%lld",(ans%lcm+lcm)%lcm);
  54. }
  55. signed main(){
  56. n=read();
  57. for (register int i=1;i<=n;++i)
  58. a[i]=read();
  59. for (register int i=1;i<=n;++i)
  60. b[i]=read();
  61. for (register int i=1;i<=n;++i)
  62. a[i]=(a[i]%b[i]+b[i])%b[i];
  63. work();
  64. return 0;
  65. }

拓展中国剩余定理(EXCRT)

题面

假设已经求出前个方程组成的同余方程组的一个解为

令:

则前个方程的通解为:

那么加入第n个式子,则问题变为:

求一个正整数使得

整理得:

而此式子可以通过拓展欧几里得求解。

那么我们就能求出前个式子的一个解:

同理处理前个式子,所以只要跑遍拓展欧几里得即可!

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. using namespace std;
  5. inline int read()
  6. {
  7. int x=0;
  8. char c=getchar();
  9. bool f=0;
  10. while (!isdigit(c))
  11. f=f|(c=='-'),c=getchar();
  12. while (isdigit(c))
  13. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  14. return f?-x:x;
  15. }
  16. int n;
  17. int a[100005],b[100005];
  18. int x,y;
  19. inline int mul(int a,int b,int m)
  20. {
  21. int ans=0;
  22. while (b)
  23. {
  24. if (b&1)
  25. ans=(ans+a)%m;
  26. a=(a<<1)%m,b>>=1;
  27. }
  28. return ans;
  29. }
  30. inline int exgcd(int a,int b)
  31. {
  32. if (!b)
  33. {
  34. x=1,y=0;
  35. return a;
  36. }
  37. int gcd=exgcd(b,a%b);
  38. int x0=x,y0=y;
  39. x=y0,y=x0-a/b*y0;
  40. return gcd;
  41. }
  42. inline void work()
  43. {
  44. int lcm=b[1],ans=a[1];
  45. for (register int i=2;i<=n;++i)
  46. {
  47. int cnt=(a[i]-ans%b[i]+b[i])%b[i],gcd=exgcd(lcm,b[i]),mod=b[i]/gcd;
  48. x=mul(x,cnt/gcd,mod);
  49. ans=ans+lcm*x,lcm*=mod;
  50. ans=(ans%lcm+lcm)%lcm;
  51. }
  52. printf("%lld",(ans%lcm+lcm)%lcm);
  53. }
  54. signed main(){
  55. n=read();
  56. for (register int i=1;i<=n;++i)
  57. b[i]=read(),a[i]=read();//注意是倒着读的。
  58. work();
  59. return 0;
  60. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注