[关闭]
@910935974 2019-08-05T15:49:15.000000Z 字数 25136 阅读 515

算法笔记——数学相关

算法 数学


高精度

  1. void init(int a[])
  2. {
  3. string s;
  4. cin>>s;
  5. a[0]=s.lenth();
  6. for(int i=1;i<=a[0];++i)
  7. a[i]=s[a[0]-i]-'0';
  8. }

加法进位:

  1. c[i]=a[i]+b[i];
  2. if(c[i]>=10)
  3. {
  4. c[i]%=10;
  5. ++c[i+1];
  6. }

减法借位:

  1. if(a[i]<b[i])
  2. {
  3. --a[i+1];
  4. a[i]+=10;
  5. }
  6. c[i]=a[i]-b[i];

乘法进位:

  1. c[i+j-1]=a[i]*b[j]+x+c[i+j-1];
  2. x=c[i+j-1]/10;
  3. c[i+j-1]%=10;

商和余数:视情况而定

高精度加法(模板:A+B problem 高精

  1. #include<cstdio>
  2. #include<cstring>
  3. using namespace std;
  4. char a1[505],b1[505];
  5. int len1,len2,len3;
  6. int a[505],b[505],c[505];
  7. int main()
  8. {
  9. scanf("%s%s",a1,b1);
  10. len1=strlen(a1);
  11. len2=strlen(b1);
  12. for(int i=0;i<len1;++i)
  13. a[len1-i]=a1[i]-'0';
  14. for(int i=0;i<len2;++i)
  15. b[len2-i]=b1[i]-'0';
  16. len3=1;
  17. int x=0;
  18. while(len3<=len1||len3<=len2)
  19. {
  20. c[len3]=a[len3]+b[len3]+x;
  21. x=c[len3]/10;
  22. c[len3]%=10;
  23. len3++;
  24. }
  25. c[len3]=x;
  26. if(c[len3]==0)len3--;
  27. for(int i=len3;i>=1;--i)
  28. printf("%d",c[i]);
  29. return 0;
  30. }

高精度减法(模板:高精度减法

  1. #include<cstdio>
  2. #include<cstring>
  3. #define maxn 10005
  4. using namespace std;
  5. char a1[maxn],b1[maxn],m[maxn];
  6. int len1,len2,len3,flag;
  7. int a[maxn],b[maxn],c[maxn];
  8. int main()
  9. {
  10. scanf("%s%s",a1,b1);
  11. if(strlen(a1)<strlen(b1)||(strlen(a1)==strlen(b1)&&strcmp(a1,b1)<0))
  12. {
  13. flag=1;
  14. strcpy(m,a1);
  15. strcpy(a1,b1);
  16. strcpy(b1,m);
  17. }//交换数组
  18. len1=strlen(a1);
  19. len2=strlen(b1);
  20. for(int i=0;i<len1;++i)a[len1-i]=a1[i]-'0';
  21. for(int i=0;i<len2;++i)b[len2-i]=b1[i]-'0';
  22. len3=1;
  23. int x=0;
  24. while(len3<=len1||len3<=len2)
  25. {
  26. if(a[len3]<b[len3])
  27. {
  28. a[len3]+=10;
  29. a[len3+1]--;
  30. }//借位
  31. c[len3]=a[len3]-b[len3];
  32. len3++;
  33. }
  34. while(c[len3]==0&&len3>1)len3--;
  35. if(len3==1&&c[1]==0)
  36. {
  37. printf("0");
  38. return 0;
  39. }
  40. if(flag)
  41. printf("-");
  42. for(int i=len3;i>=1;--i)
  43. printf("%d",c[i]);
  44. return 0;
  45. }

高精度乘法(模板:A*B problem

  1. #include<cstdio>
  2. #include<cstring>
  3. #define maxn 5005
  4. using namespace std;
  5. char a1[maxn],b1[maxn];
  6. int len1,len2,len3;
  7. int a[maxn],b[maxn],c[maxn];
  8. int main()
  9. {
  10. scanf("%s%s",a1,b1);
  11. len1=strlen(a1);
  12. len2=strlen(b1);
  13. for(int i=0;i<len1;++i)a[len1-i]=a1[i]-'0';
  14. for(int i=0;i<len2;++i)b[len2-i]=b1[i]-'0';
  15. for(int i=1;i<=len1;++i)
  16. {
  17. int x=0;
  18. for(int j=1;j<=len2;++j)
  19. {
  20. c[i+j-1]+=a[i]*b[j]+x;
  21. x=c[i+j-1]/10;
  22. c[i+j-1]%=10;
  23. }
  24. c[i+len2]=x;
  25. }
  26. len3=len1+len2;
  27. while(c[len3]==0&&len3>1)len3--;
  28. for(int i=len3;i>=1;--i)
  29. printf("%d",c[i]);
  30. return 0;
  31. }

高精度除法(高精除以低精)(模板:A/B problem

  1. #include<cstdio>
  2. #include<cstring>
  3. #define maxn 10005
  4. using namespace std;
  5. int len1,len2;
  6. int b,a[maxn],c[maxn];
  7. char a1[maxn];
  8. int main()
  9. {
  10. scanf("%s%d",a1,&b);
  11. len1=strlen(a1);
  12. for(int i=0;i<len1;++i)a[i+1]=a1[i]-'0';
  13. int x=0;
  14. for(int i=1;i<=len1;++i)
  15. {
  16. c[i]=(x*10+a[i])/b;
  17. x=(x*10+a[i])%b;
  18. }
  19. len2=1;
  20. while(c[len2]==0&&len2<len1)len2++;
  21. for(int i=len2;i<=len1;++i)
  22. printf("%d",c[i]);
  23. return 0;
  24. }

高精度除法(高精除以高精)(模板:A/B problem II

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #define maxn 1005
  5. using namespace std;
  6. int a[maxn],b[maxn],c[maxn];
  7. void read(int a[])
  8. {
  9. string s;
  10. cin>>s;
  11. a[0]=s.length();
  12. for(int i=1;i<=a[0];++i)
  13. a[i]=s[a[0]-i]-'0';
  14. }//读入
  15. void print(int a[])
  16. {
  17. if(a[0]==0)
  18. {
  19. printf("0\n");
  20. return;
  21. }
  22. for(int i=a[0];i>0;--i)
  23. printf("%d",a[i]);
  24. printf("\n");
  25. return;
  26. }//输出
  27. int cmp(int a[],int b[])
  28. {
  29. if(a[0]>b[0])return 1;
  30. if(a[0]<b[0])return -1;
  31. for(int i=a[0];i>0;--i)
  32. {
  33. if(a[i]>b[i])return 1;
  34. if(a[i]<b[i])return -1;
  35. }
  36. return 0;
  37. }//比较数组a,b的大小
  38. void jian(int a[],int b[])
  39. {
  40. int flag;
  41. flag=cmp(a,b);
  42. if(flag==0)
  43. {
  44. a[0]=0;
  45. return;
  46. }//相等
  47. if(flag==1)
  48. {
  49. for(int i=1;i<=a[0];++i)
  50. {
  51. if(a[i]<b[i])
  52. {
  53. a[i+1]--;
  54. a[i]+=10;
  55. }//借位
  56. a[i]-=b[i];
  57. }
  58. while(a[0]>0&&a[a[0]]==0)a[0]--;
  59. return;
  60. }
  61. }//模拟减法
  62. void numcpy(int p[],int q[],int del)
  63. {
  64. for(int i=1;i<=p[0];++i)
  65. q[i+del-1]=p[i];
  66. q[0]=p[0]+del-1;
  67. }//数组复制
  68. void chugao(int a[],int b[],int c[])
  69. {
  70. int tmp[maxn];
  71. c[0]=a[0]-b[0]+1;
  72. for(int i=c[0];i>=1;--i)
  73. {
  74. memset(tmp,0,sizeof(tmp));
  75. numcpy(b,tmp,i);
  76. while(cmp(a,tmp)>=0)
  77. {
  78. c[i]++;
  79. jian(a,tmp);
  80. }
  81. }
  82. while(c[0]>0&&c[c[0]]==0)c[0]--;
  83. return;
  84. }
  85. int main()
  86. {
  87. read(a);read(b);
  88. chugao(a,b,c);
  89. print(c);
  90. print(a);//输出余数
  91. return 0;
  92. }

乘法逆元

联系小学学过的倒数,,在中如果贸然直接除以,可能会出问题。然而如果对于一个数,若存在另一个数,使模上一个数等于1,那么这个数就是的逆元。

1.递推法:(p是模数)
2.拓展欧几里得法:
先预处理一遍阶乘,求得逆元即对的阶乘做一遍拓展欧几里得;

1.逆元求组合数;
2.优雅地除以一个数(化除为乘);

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define M 30000000
  4. //map<long long,long long>inv;
  5. int n,p;
  6. long long inv[M+5];
  7. void niyuan(int n,int p)
  8. {
  9. for(int i=2;i<=n;i++)
  10. {
  11. inv[i]=-p/i*inv[p%i];
  12. while(inv[i]<0)
  13. inv[i]+=p;
  14. }
  15. }
  16. int main()
  17. {
  18. scanf("%d%d",&n,&p);
  19. inv[1]=1;
  20. niyuan(n,p);
  21. for(int i=1;i<=n;i++)
  22. printf("%I64d\n",inv[i]);
  23. return 0;
  24. }

2.拓展欧几里得法:
预处理阶乘:

  1. a[0]=1
  2. for(int i=1;i<=n+m;i++)
  3. a[i]=a[i-1]*i%mod;//前缀积

求逆元:

  1. void exgcd(ll a,ll b,ll &x,ll &y)
  2. {
  3. if(b==0)
  4. {
  5. x=1;y=0;
  6. return;
  7. }
  8. ll xx,yy;
  9. exgcd(b,a%b,xx,yy);
  10. x=yy;
  11. y=xx-(a/b)*yy;
  12. }
  13. ll inv(ll sum)
  14. {
  15. ll x,y;
  16. exgcd(sum,mod,x,y);
  17. x=(x%mod+mod)%mod;
  18. return x;
  19. }//求逆元,sum为a[i]

3.快速幂求逆元:
主函数内:

  1. ans=inv(i,mod-2);

求逆元:

  1. int inv(int x,int y)
  2. {
  3. int ans=1;
  4. while(y)
  5. {
  6. if(y&1)ans=(ans*x)%mod;
  7. y>>=1;
  8. x=(x*x)%mod;
  9. }
  10. ans%=mod;
  11. return ans;
  12. }//快速幂求逆元

习题:
好像逆元没有什么裸题吧,不过倒是有一道模板题:【模板】乘法逆元
还有一道神仙题(滑稽):【模板】乘法逆元2(其实这道题与逆元没有什么关系)


排列组合

排列的分类:
1.选排列
最普通的排列,从个数选出个数的方案数。

2.错位排列(错排)
个数选出个数,数不能在第个位置上的方案数。

3.圆排列
个数选出个数围成一个圈的方案数。

,若,则有(n-1)!种。

模板:逆元求组合数

  1. #include<cstdio>
  2. #define ll long long
  3. using namespace std;
  4. ll t,n,m,mod;
  5. ll a[100005];
  6. void exgcd(ll a,ll b,ll &x,ll &y)
  7. {
  8. if(b==0)
  9. {
  10. x=1;y=0;
  11. return;
  12. }
  13. ll xx,yy;
  14. exgcd(b,a%b,xx,yy);
  15. x=yy;
  16. y=xx-(a/b)*yy;
  17. }
  18. ll inv(ll sum)
  19. {
  20. ll x,y;
  21. exgcd(sum,mod,x,y);
  22. x=(x%mod+mod)%mod;
  23. return x;
  24. }//求逆元
  25. ll C(ll x,ll y,ll mod)
  26. {
  27. if(x<y)
  28. return 0;
  29. return a[x]*inv(a[y])%mod*inv(a[x-y])%mod;
  30. }//求组合数
  31. int main()
  32. {
  33. a[0]=1;
  34. scanf("%I64d",&t);
  35. while(t--)
  36. {
  37. scanf("%I64d%I64d%I64d",&n,&m,&mod);
  38. for(int i=1;i<=n+m;i++)
  39. a[i]=a[i-1]*i%mod;//前缀积
  40. printf("%I64d\n",C(n+m-1,m,mod));
  41. }
  42. return 0;
  43. }
  44. //方法:求C(n-1,m+n-1)%mod

应用:
1.夹棍法;
2.求杨辉三角;
3.二项式定理;


二项式定理

应用:杨辉三角


质数的判定和应用

1.暴力枚举法:
对于一个数,从2开始枚举到(一个数最多有个约数),若存在,使得%=0,则为合数,否则为质数。注意特判1和2.
若求1~中的所有质数,那么该算法的复杂度为

2.普通筛法:
假如求1~50的质数,一开始如下:

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 46 47 48 49 50

划掉1,从2开始枚举,划掉除2以外2的质数:

2 3 5 7 9
11 13 15 17 19
21 23 25 27 29
31 33 35 37 39
41 43 45 47 49

接下来,划掉除3以外3的倍数:

2 3 5 7
11 13 17 19
23 25 29
31 35 37
41 43 47 49

划掉除5以外5的倍数:

2 3 5 7
11 13 17 19
23 29
31 37
41 43 47 49

划掉除7以外7的倍数:

2 3 5 7
11 13 17 19
23 29
31 37
41 43 47

到这里我们就筛完了。但细心地你会发现一个问题:一个数可能会被筛多次。举个例子,6在筛2的倍数时会被筛掉,在筛3的倍数时同样会被筛掉,这样就增加了此算法的事件复杂度。有没有办法使一个合数只会被筛一次呢?

3.线性筛:
定义两个数组:表示是否是质数,表示从小到大找到的质数。若,则吧加入进质数数组里。接下来,无论是否是质数,从1开始枚举,将标记为合数,若%=0,说明的倍数,跳出循环,保证每一个合数只会被筛一次。
举个例子,当为4时,质数数组有2和3,枚举质数2,标记4*2=8为合数。因为4是2的倍数,跳出循环。假如不跳出循环,那么下一个被枚举到的合数就是4*3=12,而12应该在枚举6*2时筛到,多筛了一遍,增加了时间复杂度。

4.Miller-Rabin素性测试:
玄学算法。
费马小定理:若是质数,则,但不是所有数只要满足就是质数,所以光有这一个定理还不够;
二次探测定理:若是质数,且,则,也就是说
Miller-Rabin的流程:
是大于2的奇数,另,d为奇数;
随机选择一个
首先判断费马小定理是否成立:
若符合,则由二项式定理,要么,要么存在一个比小的数满足
单次复杂度为,多次随机可保证极高正确率。

1.线性筛:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int prime[10000005],mark[10000005],c[100005],tot,m,n;
  4. void su()
  5. {
  6. int i,j;
  7. for(i=2;i<=n;i++)
  8. {
  9. if(mark[i]==0)//是素数。
  10. {
  11. tot++;prime[tot]=i;
  12. }//增加一个素数。
  13. for(j=1;j<=tot;j++)
  14. {
  15. mark[i*prime[j]]=1;
  16. if(i%prime[j]==0)
  17. break;//标记倍数为合数,若遇到倍数,则停止。
  18. }
  19. }
  20. }
  21. int main()
  22. {
  23. int k;
  24. scanf("%d%d",&n,&m);
  25. su();
  26. mark[1]=1;
  27. for(k=1;k<=m;k++)
  28. scanf("%d",&c[k]);
  29. for(k=1;k<=m;k++)
  30. if(mark[c[k]]==0)printf("Yes\n");
  31. else printf("No\n");
  32. return 0;
  33. }

2.Miller-Rabin

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int prime[10000005],mark[10000005],c[100005],tot,m,n;
  4. void su()
  5. {
  6. int i,j;
  7. for(i=2;i<=n;i++)
  8. {
  9. if(mark[i]==0)//是素数。
  10. {
  11. tot++;prime[tot]=i;
  12. }//增加一个素数。
  13. for(j=1;j<=tot;j++)
  14. {
  15. mark[i*prime[j]]=1;
  16. if(i%prime[j]==0)
  17. break;//标记倍数为合数,若遇到倍数,则停止。
  18. }
  19. }
  20. }
  21. int main()
  22. {
  23. int k;
  24. scanf("%d%d",&n,&m);
  25. su();
  26. mark[1]=1;
  27. for(k=1;k<=m;k++)
  28. scanf("%d",&c[k]);
  29. for(k=1;k<=m;k++)
  30. if(mark[c[k]]==0)printf("Yes\n");
  31. else printf("No\n");
  32. return 0;
  33. }#include<bits/stdc++.h>
  34. using namespace std;
  35. int prime[10000005],mark[10000005],c[100005],tot,m,n;
  36. void su()
  37. {
  38. int i,j;
  39. for(i=2;i<=n;i++)
  40. {
  41. if(mark[i]==0)//是素数。
  42. {
  43. tot++;prime[tot]=i;
  44. }//增加一个素数。
  45. for(j=1;j<=tot;j++)
  46. {
  47. mark[i*prime[j]]=1;
  48. if(i%prime[j]==0)
  49. break;//标记倍数为合数,若遇到倍数,则停止。
  50. }
  51. }
  52. }
  53. int main()
  54. {
  55. int k;
  56. scanf("%d%d",&n,&m);
  57. su();
  58. mark[1]=1;
  59. for(k=1;k<=m;k++)
  60. scanf("%d",&c[k]);
  61. for(k=1;k<=m;k++)
  62. if(mark[c[k]]==0)printf("Yes\n");
  63. else printf("No\n");
  64. return 0;
  65. }

大多数时候作为一种辅助算法,帮助解题(如Pollard-Rho)。
没什么裸题,都是很简单的那种。
但是线性筛素数还可以顺带预处理除一些积性函数,如欧拉函数,莫比乌斯函数等等。
一些题(双倍经验):
1.樱花
2.樱花


约数

1.将分解质因数:;
2.约数个数:;
3.约数和:;
4.约数函数:;
以上这些东西,我也看不懂。
例题:
1.[AHOI2007]密码箱(数据过水);
2.[HAOI2007]反素数;
3.[JLOI2014]聪明的燕姿;

最大公因数的性质:
1.;
2.;

最大公因数的求法:
1.更相减损术;
2.欧几里得法。

代码:

  1. #include<cstdio>
  2. using namespace std;
  3. int n,m;
  4. int gcd(int x,int y)
  5. {
  6. return (y==0)?x:gcd(y,x%y);
  7. }
  8. int main()
  9. {
  10. scanf("%d%d",&n,&m);
  11. printf("%d",gcd(n,m));
  12. return 0;
  13. }

例题:
1.NOIp2009 Hankson的趣味题


拓展欧几里得

1.求解同余方程:
转化为,移项,另,转化为,但注意必须,否则无解。

2.求解不定方程:
对于,若满足,方程有解;反之则无解。

斐蜀定理:一定有解。

在欧几里得算法的最后一步时,有,因为,所以此时有一组解
时,递归求解方程,直到

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int a,b,x,y,s;
  4. int gcd(int a,int b)
  5. {
  6. int r=a%b;
  7. if(r==0)
  8. return r;
  9. a=b;b=r;
  10. gcd(a,b);
  11. }
  12. void exgcd(int a,int b,int &x,int &y)
  13. {
  14. if(b==0)
  15. {
  16. x=1;
  17. y=0;
  18. return;
  19. }
  20. int c=a-(a/b)*b,xx,yy;
  21. exgcd(b,c,xx,yy);
  22. x=yy;y=xx-(a/b)*yy;
  23. }
  24. int main()
  25. {
  26. scanf("%d%d",&a,&b);
  27. s=gcd(a,b);
  28. exgcd(a,b,x,y);
  29. printf("%d %d",x,y);
  30. return 0;
  31. }

其实,求解同余方程,还有一种方法:

  1. a%=p;b%=p;
  2. if(a==0&&b!=0)
  3. printf("Orz, I cannot find x!\n");
  4. else
  5. printf("%lld\n",quick(a,p-2,p)*b%p);
  6. continue;

1.NOIP2012 同余方程
2.青蛙的约会(怎么变蓝题了,我刚做的时候不是绿题吗


大步小步算法(BSGS)

  1. #include<cstdio>
  2. #include<cmath>
  3. #include<map>
  4. #define ll long long
  5. using namespace std;
  6. ll a,b,p,ans;
  7. map<ll,ll>mp;
  8. map<ll,bool>vis;
  9. ll quick(ll x,ll y)
  10. {
  11. ll ret=1;
  12. while(y)
  13. {
  14. if(y&1)ret=(ret*x)%p;
  15. x=(x*x)%p;
  16. y>>=1;
  17. }
  18. return ret;
  19. }
  20. ll bsgs(ll a,ll b)
  21. {
  22. mp.clear();vis.clear();
  23. ll q=sqrt(p);
  24. b%=p;
  25. for(ll i=0;i<=q;++i)
  26. {
  27. ll ret=b*quick(a,i)%p;
  28. mp[ret]=i;
  29. vis[ret]=true;
  30. }
  31. a=quick(a,q);
  32. if(a==0)return b==0?1:-1;
  33. for(ll i=0;i<=q;++i)
  34. {
  35. ll ret=quick(a,i);
  36. if(vis[ret])
  37. {
  38. ll j=mp[ret];
  39. if(j>=0&&i*q-j>=0)
  40. return i*q-j;
  41. }
  42. }
  43. return -1;
  44. }
  45. int main()
  46. {
  47. while(scanf("%lld%lld%lld",&p,&a,&b)!=EOF)
  48. {
  49. ans=bsgs(a,b);
  50. if(ans==-1)printf("no solution\n");
  51. else printf("%lld\n",ans);
  52. }
  53. return 0;
  54. }

拓展大步小步算法

与普通的bsgs类似,但可以处理模数不为质数的情况。直接贴代码了:

  1. #include<cstdio>
  2. #include<cmath>
  3. #include<map>
  4. #define ll long long
  5. using namespace std;
  6. ll a,b,p,ans;
  7. map<ll,ll>mp;
  8. map<ll,bool>vis;
  9. ll gcd(ll x,ll y)
  10. {
  11. return (y==0)?x:gcd(y,x%y);
  12. }
  13. ll quick(ll x,ll y,ll mod)
  14. {
  15. ll ret=1;
  16. while(y)
  17. {
  18. if(y&1)ret=ret*x%mod;
  19. x=x*x%mod;
  20. y>>=1;
  21. }
  22. return ret;
  23. }
  24. ll exbsgs(ll a,ll b,ll mod)
  25. {
  26. if(b==1)return 0;
  27. ll k=0,tmp=1,d;
  28. while(1)
  29. {
  30. d=gcd(a,mod);
  31. if(d==1)break;
  32. if(b%d)return -1;
  33. b/=d;mod/=d;
  34. tmp=tmp*(a/d)%mod;
  35. k++;
  36. if(tmp==b)return k;
  37. }
  38. mp.clear();
  39. vis.clear();
  40. ll mul=b,q=ceil(sqrt(mod));
  41. mp[b]=0;
  42. for(int i=1;i<=q;++i)
  43. {
  44. mul=mul*a%mod;
  45. mp[mul]=i;
  46. vis[mul]=true;
  47. }
  48. ll qq=quick(a,q,mod);
  49. mul=tmp;
  50. for(int i=1;i<=q;++i)
  51. {
  52. mul=mul*qq%mod;
  53. if(vis[mul])return i*q-mp[mul]+k;
  54. }
  55. return -1;
  56. }
  57. int main()
  58. {
  59. while(scanf("%lld%lld%lld",&p,&a,&b)!=EOF)
  60. {
  61. ans=exbsgs(a,b,p);
  62. if(ans==-1)printf("no solution\n");
  63. else printf("%lld\n",ans);
  64. }
  65. return 0;
  66. }

快速乘和快速幂

1.快速乘(乘法变加法)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. long long a,b,s,sum;
  4. long long fast(long long a,long long b,long long mod)
  5. {
  6. long long ans=0;
  7. while(b)
  8. {
  9. if(b%2)
  10. {
  11. ans+=a;
  12. ans%=mod;
  13. }
  14. a+=a;
  15. a%=mod;
  16. b/=2;
  17. }
  18. return ans;
  19. }
  20. int main()
  21. {
  22. scanf("%I64d%I64d%I64d",&a,&b,&s);
  23. sum=fast(a,b,s);
  24. printf("%I64d",sum);
  25. return 0;
  26. }

2.快速幂:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int a,b,s,sum;
  4. int fast(int a,int b,int mod)
  5. {
  6. int ans=1;
  7. while(b)
  8. {
  9. if(b%2)
  10. {
  11. ans*=a;
  12. ans%=mod;
  13. }
  14. a*=a;
  15. a%=mod;
  16. b=b>>1;
  17. }
  18. return ans;
  19. }
  20. int main()
  21. {
  22. scanf("%d%d%d",&a,&b,&s);
  23. sum=fast(a,b,s);
  24. printf("%d",sum);
  25. return 0;
  26. }

矩阵相关

1.矩阵加减:
条件:两个矩阵长宽一样,例如

减法类似;
2.矩阵乘法:
条件:第一个矩阵的行数和第二个矩阵的列数相等。

3.矩阵的幂:

  1. #include<cstdio>
  2. #include<iostream>
  3. #define mod 1000000007
  4. #define ll long long
  5. using namespace std;
  6. struct node
  7. {
  8. ll m[105][105];
  9. }a,e;//a是输入的矩阵,ans是答案矩阵,e是单位矩阵
  10. ll n,k;
  11. node mul(node x,node y)
  12. {
  13. node nw;
  14. for(int i=1;i<=n;i++)
  15. for(int j=1;j<=n;j++)
  16. nw.m[i][j]=0;
  17. for(int i=1;i<=n;i++)
  18. for(int j=1;j<=n;j++)
  19. for(int k=1;k<=n;k++)
  20. {
  21. nw.m[i][j]=nw.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod;
  22. }
  23. return nw;
  24. }//矩阵乘法
  25. node quickk(node x,ll y)
  26. {
  27. node pre=e;
  28. while(y)
  29. {
  30. if(y&1)
  31. pre=mul(pre,x);
  32. x=mul(x,x);
  33. y>>=1;
  34. }
  35. return pre;
  36. }//快速幂主体
  37. int main()
  38. {
  39. scanf("%lld%lld",&n,&k);
  40. for(int i=1;i<=n;i++)
  41. for(int j=1;j<=n;j++)
  42. scanf("%lld",&a.m[i][j]);
  43. for(int i=1;i<=n;i++)
  44. e.m[i][i]=1;//构造单位矩阵
  45. node ans=quickk(a,k);
  46. for(int i=1;i<=n;i++)
  47. {
  48. for(int j=1;j<=n;j++)
  49. printf("%lld ",ans.m[i][j]%mod);
  50. printf("\n");
  51. }
  52. return 0;
  53. }
  1. #include<cstdio>
  2. #define ll long long
  3. #define mod 1000000007
  4. using namespace std;
  5. struct node
  6. {
  7. ll m[4][13];
  8. }stp,e,ans;
  9. ll n;
  10. int t;
  11. node mul(node x,node y)
  12. {
  13. node nw;
  14. for(int i=1;i<=3;i++)
  15. for(int j=1;j<=3;j++)
  16. nw.m[i][j]=0;
  17. for(int i=1;i<=3;i++)
  18. for(int j=1;j<=3;j++)
  19. for(int k=1;k<=3;k++)
  20. {
  21. nw.m[i][j]=(nw.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod)%mod;
  22. }
  23. return nw;
  24. }//矩阵乘法
  25. node quickk(node x,ll y)
  26. {
  27. node pre=e;
  28. while(y)
  29. {
  30. if(y&1)
  31. pre=mul(pre,x);
  32. x=mul(x,x);
  33. y>>=1;
  34. }
  35. return pre;
  36. }//快速幂主体
  37. int main()
  38. {
  39. stp.m[1][14]=1;
  40. stp.m[1][15]=1;
  41. stp.m[2][16]=1;
  42. stp.m[3][17]=1;
  43. for(int i=1;i<=3;i++)
  44. e.m[i][i]=1;
  45. scanf("%d",&t);
  46. while(t--)
  47. {
  48. for(int i=1;i<=3;i++)
  49. for(int j=1;j<=3;j++)
  50. ans.m[i][j]=0;
  51. scanf("%lld",&n);
  52. ans=quickk(stp,n-1);
  53. printf("%lld\n",ans.m[1][18]);
  54. }
  55. return 0;
  56. }

例题:
1.【模板】矩阵加速(数列)
2.斐波那契数列


欧拉函数

1.线性求欧拉函数(求素数时顺便求):

  1. #include<cstdio>
  2. #define maxn 1000005
  3. using namespace std;
  4. int n;
  5. int phi[maxn],prime[maxn],tot,ans;
  6. bool mark[maxn];
  7. void getphi()
  8. {
  9. phi[1]=1;
  10. for(int i=2;i<=n;++i)
  11. {
  12. if(!mark[i])
  13. {
  14. prime[++tot]=i;
  15. phi[i]=i-1;
  16. }//质数的phi
  17. for(int j=1;j<=tot;++j)
  18. {
  19. if(i*prime[j]>n)break;
  20. mark[i*prime[j]]=1;//标记合数
  21. if(i%prime[j]==0)
  22. {
  23. phi[i*prime[j]]=phi[i]*prime[j];
  24. break;
  25. }
  26. else phi[i*prime[j]]=phi[i]*(prime[j]-1);
  27. }
  28. }
  29. }
  30. int main()
  31. {
  32. scanf("%d",&n);
  33. getphi();
  34. for(int i=1;i<=n;++i)
  35. printf("%d ",phi[i]);
  36. return 0;
  37. }

2.非线性:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int phi(int x)
  4. {
  5. int ans=x;
  6. for(int i=2;i*i<=x;i++)
  7. {
  8. if(x%i==0)
  9. {
  10. ans=ans/i*(i-1);
  11. while(x%i==0)x/=i;
  12. }
  13. }
  14. if(x>1)ans=ans/x*(x-1);//x为质数
  15. return ans;
  16. }
  17. int main()
  18. {
  19. int n;
  20. while(scanf("%d",&n)==1)
  21. printf("%d\n",phi(n));
  22. return 0;
  23. }

欧拉定理及费马小定理

  1. #include<cstdio>
  2. #define ll long long
  3. using namespace std;
  4. ll a,b,m;
  5. ll tmp,phi,flag;
  6. char ch;
  7. ll quick(ll x,ll y)
  8. {
  9. ll ans=1;
  10. while(y)
  11. {
  12. if(y&1)ans=ans*x%m;
  13. x=x*x%m;
  14. y>>=1;
  15. }
  16. return ans;
  17. }
  18. int main()
  19. {
  20. scanf("%lld%lld",&a,&m);
  21. tmp=phi=m;
  22. for(ll i=2;i*i<=m;++i)
  23. {
  24. if(tmp%i)continue;
  25. phi-=phi/i;
  26. while(tmp%i==0)
  27. tmp/=i;
  28. }
  29. if(tmp>1)
  30. phi-=phi/tmp;
  31. while((ch=getchar())<'0'||ch>'9');
  32. b=ch-'0';
  33. if(b>=phi)
  34. {
  35. flag=1;
  36. b%=phi;
  37. }
  38. while((ch=getchar())>='0'&&ch<='9')
  39. {
  40. b=b*10+ch-'0';
  41. if(b>=phi)
  42. {
  43. flag=1;
  44. b%=phi;
  45. }
  46. }
  47. if(flag)b+=phi;
  48. printf("%lld",quick(a,b));
  49. return 0;
  50. }

模板题:【模板】欧拉定理


中国剩余定理

  1. #include<cstdio>
  2. #define maxn 100005
  3. using namespace std;
  4. int n;
  5. int a[maxn],m[maxn];
  6. void exgcd(int a,int b,int &x,int &y)
  7. {
  8. if(b==0)
  9. {
  10. x=1;
  11. y=0;
  12. return;
  13. }
  14. int c=a-(a/b)*b,xx,yy;
  15. exgcd(b,c,xx,yy);
  16. x=yy;y=xx-(a/b)*yy;
  17. }
  18. int china(int w[],int b[],int k)
  19. {
  20. int x,y,a=0,m,n=1;
  21. for(int i=1;i<=k;++i)
  22. n*=w[i];
  23. for(int i=1;i<=k;++i)
  24. {
  25. m=n/w[i];
  26. exgcd(w[i],m,x,y);
  27. a=(a+y*m*b[i])%n;
  28. }
  29. if(a>0)return a;
  30. else return a+n;
  31. }
  32. int main()
  33. {
  34. scanf("%d",&n);
  35. for(int i=1;i<=n;++i)
  36. scanf("%d%d",&m[i],&a[i]);
  37. printf("%d",china(m,a,n));
  38. return 0;
  39. }

拓展中国剩余定理

  1. #include<cstdio>
  2. #define ll long long
  3. using namespace std;
  4. ll n,a,b,tot,M;
  5. ll ans,x,y,r;
  6. ll flag=1,mul;
  7. ll exgcd(ll a,ll b,ll &x,ll &y)
  8. {
  9. if(b==0)
  10. {
  11. x=1;y=0;
  12. return a;
  13. }
  14. ll d=exgcd(b,a%b,x,y);
  15. ll t=y;
  16. y=x-(a/b)*y;
  17. x=t;
  18. return d;
  19. }//求解同余方程
  20. ll quick_mul(ll nw,ll aim,ll mod)
  21. {
  22. ll res=0;
  23. while(aim>0)
  24. {
  25. if(aim&1)res=(res+nw)%mod;
  26. nw=(nw+nw)%mod;
  27. aim>>=1;
  28. }
  29. return res;
  30. }//龟速乘QAQ(模板)
  31. int main()
  32. {
  33. scanf("%I64d",&n);
  34. while(n--)
  35. {
  36. x=0;
  37. y=0;
  38. tot++;//tot计数
  39. scanf("%I64d%I64d",&a,&b);
  40. if(tot==1)
  41. {
  42. ans=b;
  43. M=a;
  44. continue;
  45. }//初始化
  46. r=exgcd(M,a,x,y);//求最大公因数
  47. mul=((b-ans)%a+a)%a;
  48. x=quick_mul(x,mul/r,a);
  49. if((b-ans)%r!=0)
  50. {
  51. flag=0;
  52. continue;
  53. }//判断无解
  54. ans=ans+(x*M);
  55. M=(M*a)/r;//更新最小公倍数
  56. ans=(ans%M+M)%M;//保证ans为正值
  57. }
  58. if(flag)
  59. printf("%I64d",ans);
  60. else
  61. printf("No");
  62. return 0;
  63. }

卢卡斯定理

  1. #include<cstdio>
  2. #define ll long long
  3. using namespace std;
  4. ll t,n,m,p;
  5. ll quick(ll x,ll y)
  6. {
  7. ll ret=1;
  8. while(y)
  9. {
  10. if(y&1)ret=(ret*x)%p;
  11. x=(x*x)%p;
  12. y>>=1;
  13. }
  14. return ret;
  15. }
  16. ll C(ll n,ll m)
  17. {
  18. if(n<m)return 0;
  19. if(m>n-m)m=n-m;
  20. ll s1=1,s2=1;
  21. for(ll i=0;i<m;++i)
  22. {
  23. s1=s1*(n-i)%p;
  24. s2=s2*(i+1)%p;
  25. }
  26. return s1*quick(s2,p-2)%p;
  27. }
  28. ll lucas(ll n,ll m)
  29. {
  30. if(m==0)return 1;
  31. return C(n%p,m%p)*lucas(n/p,m/p)%p;
  32. }
  33. int main()
  34. {
  35. scanf("%lld",&t);
  36. while(t--)
  37. {
  38. scanf("%lld%lld%lld",&n,&m,&p);
  39. printf("%lld\n",lucas(m+n,m));
  40. }
  41. return 0;
  42. }

模板题:【模板】卢卡斯定理


拓展卢卡斯定理

  1. #include<cstdio>
  2. #include<cmath>
  3. #define ll long long
  4. #define maxn 1000005
  5. using namespace std;
  6. ll n,m,p;
  7. ll A[maxn],B[maxn];
  8. ll gcd(ll a,ll b)
  9. {
  10. return (b==0)?a:gcd(b,a%b);
  11. }
  12. void exgcd(ll a,ll b,ll &x,ll &y)
  13. {
  14. if(b==0)
  15. {
  16. x=1;
  17. y=0;
  18. return;
  19. }
  20. ll c=a-(a/b)*b,xx,yy;
  21. exgcd(b,c,xx,yy);
  22. x=yy;y=xx-(a/b)*yy;
  23. }
  24. ll inv(ll a,ll b)
  25. {
  26. ll x,y;
  27. exgcd(a,b,x,y);
  28. return (x+b)%b;
  29. }
  30. ll quick(ll x,ll y,ll mod)
  31. {
  32. ll ret=1;
  33. while(y)
  34. {
  35. if(y&1)ret=(ret*x)%mod;
  36. x=(x*x)%mod;
  37. y>>=1;
  38. }
  39. return ret;
  40. }
  41. ll f(ll n,ll x,ll y)
  42. {
  43. if(n==0)return 1;
  44. ll ret=1;
  45. for(ll i=2;i<=y;++i)
  46. {
  47. if(i%x)
  48. ret=(ret*i)%y;
  49. }
  50. ret=quick(ret,n/y,y);
  51. for(ll i=2;i<=n%y;++i)
  52. {
  53. if(i%x)
  54. ret=(ret*i)%y;
  55. }
  56. return ret*f(n/x,x,y)%y;
  57. }
  58. ll china(ll x,ll y)
  59. {
  60. return x*inv(p/y,y)%p*(p/y)%p;
  61. }
  62. ll C(ll n,ll m,ll x,ll y)
  63. {
  64. ll f1=f(n,x,y),f2=f(m,x,y),f3=f(n-m,x,y);
  65. ll ret=0;
  66. for(ll i=n;i;i/=x)ret+=i/x;
  67. for(ll i=m;i;i/=x)ret-=i/x;
  68. for(ll i=n-m;i;i/=x)ret-=i/x;
  69. return f1*inv(f2,y)%y*inv(f3,y)%y*quick(x,ret,y)%y;
  70. }
  71. ll exlucas(ll n,ll m,ll mod)
  72. {
  73. ll ret=0,q=sqrt(mod)+5,x,tmp=mod;
  74. for(ll i=2;i<=q;++i)
  75. {
  76. if(tmp%i)continue;
  77. x=1;
  78. while(tmp%i==0)
  79. {
  80. x*=i;
  81. tmp/=i;
  82. }
  83. ret=(ret+china(C(n,m,i,x),x))%mod;
  84. }
  85. if(tmp>1)ret=(ret+china(C(n,m,tmp,tmp),tmp))%mod;
  86. return ret;
  87. }
  88. int main()
  89. {
  90. scanf("%lld%lld%lld",&n,&m,&p);
  91. printf("%lld",exlucas(n,m,p));
  92. return 0;
  93. }

狄利克雷卷积


莫比乌斯函数

  1. #include<cstdio>
  2. #define maxn 1000005
  3. using namespace std;
  4. int n;
  5. int mu[maxn],prime[maxn],tot,ans;
  6. bool mark[maxn];
  7. void getmu()
  8. {
  9. mu[1]=1;
  10. for(int i=2;i<=n;++i)
  11. {
  12. if(!mark[i])
  13. {
  14. prime[++tot]=i;
  15. mu[i]=-1;
  16. }//质数的phi
  17. for(int j=1;j<=tot;++j)
  18. {
  19. if(i*prime[j]>n)break;
  20. mark[i*prime[j]]=1;//标记合数
  21. if(i%prime[j]==0)
  22. {
  23. mu[i*prime[j]]=0;
  24. break;
  25. }
  26. else mu[i*prime[j]]=-mu[i];
  27. }
  28. }
  29. }
  30. int main()
  31. {
  32. scanf("%d",&n);
  33. getmu();
  34. for(int i=1;i<=n;++i)
  35. printf("%d ",mu[i]);
  36. return 0;
  37. }

莫比乌斯反演

  1. #include<cstdio>
  2. #include<algorithm>
  3. #define ll long long
  4. #define maxn 50005
  5. using namespace std;
  6. ll t,a,b,d,tot;
  7. ll prime[maxn],mu[maxn],sum[maxn];
  8. bool mark[maxn];
  9. void getmu()
  10. {
  11. mu[1]=1;
  12. for(ll i=2;i<=50000;++i)
  13. {
  14. if(!mark[i])
  15. {
  16. prime[++tot]=i;
  17. mu[i]=-1;
  18. }
  19. for(ll j=1;j<=tot;++j)
  20. {
  21. if(i*prime[j]>50000)break;
  22. mark[i*prime[j]]=1;
  23. if(i%prime[j]==0)
  24. {
  25. mu[i*prime[j]]=0;
  26. break;
  27. }
  28. else mu[i*prime[j]]=-mu[i];
  29. }
  30. }
  31. for(ll i=1;i<=50000;++i)
  32. sum[i]=sum[i-1]+mu[i];
  33. }
  34. int main()
  35. {
  36. getmu();
  37. scanf("%lld",&t);
  38. while(t--)
  39. {
  40. ll n,ans=0;
  41. scanf("%lld%lld%lld",&a,&b,&d);
  42. n=min(a,b);
  43. for(ll l=1,r;l<=n;l=r+1)
  44. {
  45. r=min(a/(a/l),b/(b/l));
  46. ans+=(a/(l*d))*(b/(l*d))*(sum[r]-sum[l-1]);
  47. }
  48. printf("%lld\n",ans);
  49. }
  50. return 0;
  51. }

杜教筛

即:


用数论分块即可求出正解。
总复杂度:

  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<map>
  4. #define ll long long
  5. #define maxn 5000005
  6. #define mod 1000000007
  7. using namespace std;
  8. map<ll,ll>mp;
  9. ll n,m,tot;
  10. ll ans[maxn],mu[maxn],prime[maxn];
  11. bool mark[maxn];
  12. void getmu()
  13. {
  14. mu[1]=1;
  15. for(ll i=2;i<=m;++i)
  16. {
  17. if(!mark[i])
  18. {
  19. prime[++tot]=i;
  20. mu[i]=-1;
  21. }//质数的phi
  22. for(ll j=1;j<=tot;++j)
  23. {
  24. if(i*prime[j]>m)break;
  25. mark[i*prime[j]]=1;//标记合数
  26. if(i%prime[j]==0)
  27. {
  28. mu[i*prime[j]]=0;
  29. break;
  30. }
  31. else mu[i*prime[j]]=-mu[i];
  32. }
  33. }
  34. }
  35. inline ll f(ll n)
  36. {
  37. if(n<=5000000)return ans[n];
  38. if(mp.find(n)!=mp.end())return mp[n];
  39. ll ret=1;
  40. for(ll l=2,r;l<=n;l=r+1)
  41. {
  42. r=n/(n/l);
  43. ret-=(r-l+1)*f(n/l);
  44. ret=(ret%mod+mod)%mod;
  45. }
  46. return mp[n]=ret%mod;
  47. }
  48. int main()
  49. {
  50. scanf("%lld",&n);
  51. m=min(n,(ll)5000000);
  52. getmu();
  53. for(ll i=1;i<=m;++i)
  54. {
  55. ans[i]=(ans[i-1]+i*mu[i])%mod;
  56. ans[i]=(ans[i]+mod)%mod;
  57. }
  58. printf("%lld",f(n));
  59. return 0;
  60. }

快速傅里叶变换(FFT)

  1. #include<cstdio>
  2. #include<cmath>
  3. #include<algorithm>
  4. #define maxn 10000005
  5. using namespace std;
  6. const double pi=acos(-1.0);
  7. struct complex
  8. {
  9. double x,y;
  10. complex(double xx=0,double yy=0){x=xx,y=yy;}
  11. }a[maxn],b[maxn];
  12. int n,m;
  13. int lim=1,l,r[maxn];
  14. complex operator+(complex a,complex b)
  15. {
  16. return complex(a.x+b.x,a.y+b.y);
  17. }
  18. complex operator-(complex a,complex b)
  19. {
  20. return complex(a.x-b.x,a.y-b.y);
  21. }
  22. complex operator*(complex a,complex b)
  23. {
  24. return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
  25. }//复数运算
  26. void fft(complex *c,int flag)
  27. {
  28. for(int i=0;i<lim;++i)
  29. {
  30. if(i<r[i])
  31. swap(c[i],c[r[i]]);
  32. }//求出迭代序列
  33. for(int mid=1;mid<lim;mid<<=1)
  34. {
  35. complex wn(cos(pi/mid),flag*sin(pi/mid));
  36. for(int r=mid<<1,j=0;j<lim;j+=r)//r是右端点,j表示当前位置
  37. {
  38. complex w(1,0);
  39. for(int k=0;k<mid;++k,w=w*wn)
  40. {
  41. complex x=c[j+k],y=w*c[j+mid+k];
  42. c[j+k]=x+y;
  43. c[j+mid+k]=x-y;
  44. }
  45. }
  46. }
  47. }
  48. int main()
  49. {
  50. scanf("%d%d",&n,&m);
  51. for(int i=0;i<=n;++i)
  52. scanf("%lf",&a[i].x);
  53. for(int i=0;i<=m;++i)
  54. scanf("%lf",&b[i].x);
  55. while(lim<=n+m)
  56. {
  57. lim<<=1;
  58. l++;
  59. }
  60. for(int i=0;i<lim;++i)
  61. r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));//蝴蝶变换
  62. fft(a,1);
  63. fft(b,1);
  64. for(int i=0;i<=lim;++i)
  65. a[i]=a[i]*b[i];
  66. fft(a,-1);//傅里叶逆变换
  67. for(int i=0;i<=n+m;++i)
  68. printf("%d ",(int)(a[i].x/lim+0.5));
  69. return 0;
  70. }

快速数论变换(NTT)


*斯特林数相关算法

由于太难了,有时间再写。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注