@dxbdly
2021-03-03T14:46:24.000000Z
字数 11109
阅读 211
信息学——19寒假集训
今天是G2020寒假集训Day4了,数学专题终于来了,主讲数论。
定义如下:
最大公因数(gcd):多个数的公共因数中最大的数最小公倍数(lcm):多个数的公共倍数中最小的数
性质1:
我们一般用来表示
性质1推论:
的表示方法:
设:为的集合。
则
性质1:
性质2:
欧几里得算法是求的一种方法,其核心为:
根据此式不断递归,缩小,此算法又称辗转相除法。
拓展欧几里得适用问题:
给定求一组满足的解
拓展欧几里得的内容:
若:有满足:
则有一组特殊解:
证明如下:
设满足:
整理得:
对比方程:
所以有:
所以易得:
我们之前已经求出了一组特殊整数解。
但是如果要我们求出所有解怎么办呢?
我们可以考虑不定方程求解系的做法。
由于我们已经得到了一组特殊解,则整个解系为:
这是不定方程的基本知识,不再赘述。
拓展欧几里得多用于解,形如的不定方程
我们首先要知道,此方程有整数解的充分条件为:
那么我们可以考虑先求出的解。
直接使用拓展欧几里得定理即可。
那么只需要将,分别乘上。
//The code is from dxbdly#include<bits/stdc++.h>using namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}inline int get_gcd(int a,int b){return !b?a:get_gcd(b,a%b);}inline void Exgcd(int a,int b,int &x,int &y){if (!b)x=1,y=0;elseExgcd(b,a%b,y,x),y-=a/b*x;}int main(){int a,b,c,x,y;a=read(),b=read(),c=read();Exgcd(a,b,x,y);printf("%d %d",x*c/get_gcd(a,b),y*c/get_gcd(a,b));return 0;}
若 ,则称为在意义下的逆元,记为,也可以说是在意义下的倒数。
大多数求逆元的方法都要求且为质数。
求解逆元的方式有很多,下面给出4种求法。
注意此算法只要求,不一定为质数。
使用拓展欧几里得求解逆元是很好的选择,他的速度比其他算法稍快,并且适用性比较广。
我们可以将同余方程变为不定方程
求解这个方程的解直接套用拓展欧几里得就可以了。
//The code is from dxbdly#include<bits/stdc++.h>using namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}inline void Exgcd(int a,int b,int &x,int &y){if (!b)x=0,y=1;elseExgcd(b,a%b,x,y),y-=a/b*x;}int main(){int a,b,x,y;a=read(),b=read();Exgcd(a,b,x,y);x=(x%b+b)%b;printf("%d",x);return 0;}
这个算法需要用到费马小定理:
,其中,且为质数
(后面的章节有详细讲解)
可以发现此同余式右边正好为,且都是意义下的。
考虑将原式的替换,则:
则进一步有:
所以我们只需要用快速幂求出即可。
//The code is from dxbdly#include<bits/stdc++.h>using namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}inline int ksm(int a,int b,int p){int res=1;while (b){if (b&1)res=res*a%p;a=a*a%p,b>>=1;}return res%p;}int main(){int a,b;a=read(),b=read();printf("%d",ksm(a,b-2,b))return 0;}
考虑如何线性求,意义下的逆元。
首先要知道,
我们设,则,
把这个式子放到意义下就可以得到:
将其乘上
可得:
即
将与代入得:
而就是的逆元,所以可以线性求出。
//The code is from dxbdly#include<bits/stdc++.h>#define int long longusing namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n,p;int inv[3000005];signed main(){n=read(),p=read();inv[1]=1;for (register int i=2;i<=n;++i)inv[i]=(p-p/i)*inv[p%i]%p;for (register int i=1;i<=n;++i)printf("%lld\n",inv[i]);return 0;}
经典问题:
求的值
对于此题,我们可以先考虑在不同情况下的值
不难发现,最多只有种取值。
可以考虑:将原式根据的取值,分成几段求解。
若时均相等,则很好计算。
即,且计算的时间复杂度在
所以我们可以的枚举块,对于每一个块求解,总时间复杂度
代码如下:
//The code is from dxbdly#include<bits/stdc++.h>#define int long longusing namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n,ans;signed main(){n=read();for (register int l=1,r;l<=n;l=r+1)r=n/(n/l),ans+=(r-l+1)*(n/l);printf("%lld",ans);return 0;}
题解:
分析数据范围,显然不能暴力,们先来推式子
由题意:
将运算转化为整除运算:
整理得
那么我们可以利用整除分块解决。
那么对于
使得相等的公式为
AC代码:
//The code is from dxbdly#include<bits/stdc++.h>#define int long longusing namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f|=(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n,k;int sum;signed main(){n=read(),k=read();for (register int l=1,r;l<=n;l=r+1){int x=k/l;r=(x?min(k/x,n):n);sum+=(r-l+1)*(l+r)/2*x;}printf("%lld",n*k-sum);return 0;}
定义:
的公式
其中为的质因数。
可以理解为:
即统计,~中与互质的个数
是一个积性函数
的求法需要用到线性筛,下一点中会提到。
基本算法,不多讲,很简单。
其思想是每次确定一个质数,就将此质数所有倍数排除。
时间复杂度约
欧拉筛其实就是在埃氏筛上做出了优化,使得时间复杂度成功降到。
考虑埃氏筛的冗余,一个数可能会被多个质数所筛中,从而增大复杂度。
欧拉筛法保证了每个数都只被一个质数筛一遍,算法结束时每个数都要被筛到,所以复杂度为
先放代码:
//The code is from dxbdlyinline void get_prime(){for (register int i=2;i<=n;++i){if (!b[i])prime[++tot]=i;int x=prime[1]*i;for (register int j=1;j<=tot&&x<=n;++j,x=prime[j]*i){b[x]=1;if (!(i%prime[j]))break;}}}
注意到最后一个语句,那便是防止一个数重复被筛的关键。
我们容易得出:
那么即可在线性筛时的处理出~的值
代码:
//The code is from dxbdlyinline void get_phi(){phi[1]=1;for (register int i=2;i<=n;++i){if (!b[i])prime[++tot]=i,phi[i]=i-1;int x=prime[1]*i;for (register int j=1;j<=tot&&x<=n;++j,x=prime[j]*i){b[x]=1;if (!(i%prime[j])){phi[x]=prime[j]*phi[i];break;}phi[x]=(prime[j]-1)*phi[i];}}}
费马小定理:
若 是一个质数, 且 不是 的倍数,则有:
欧拉定理:
若 互质,则有:
拓展欧拉定理:
当 ,则有 :
题解:
我们令:
则,套用拓展欧拉定理可得
预处理出~,递归求解
递归复杂度,预处理复杂度
总复杂度:可过!
AC代码:
//The code is from dxbdly#include<bits/stdc++.h>using namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f|=(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int t,p;int prime[10000005],phi[10000005],tot;bool b[10000005];inline void get_phi(){phi[1]=1;for (register int i=2;i<=10000000;++i){if (!b[i])prime[++tot]=i,phi[i]=i-1;int x=prime[1]*i;for (register int j=1;j<=tot&&x<=10000000;++j,x=prime[j]*i){b[x]=1;if (!(i%prime[j])){phi[x]=prime[j]*phi[i];break;}phi[x]=(prime[j]-1)*phi[i];}}}inline int ksm(int a,int b,int m){int ans=1;while (b){if (b&1)ans=1ll*ans*a%m;a=1ll*a*a%m,b>>=1;}return ans;}inline int get_ans(int x){if (x<=2)return 0;return ksm(2,get_ans(phi[x])+phi[x],x);}int main(){t=read();get_phi();while (t--){p=read();printf("%d\n",get_ans(p));}return 0;}
注:部分引用自博客
问题:
求同余方程组:
其中为两两互质的整数。
求的最小非负整数解。
令:
令:为同余方程
则,一定有一个解
且,其最小非负整数解为:
代码:
//The code is from dxbdly#include<bits/stdc++.h>using namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n;int a[100005],b[100005],x,y;inline void exgcd(int a,int b){if (b==0){x=1,y=0;return ;}exgcd(b,a%b);int x0=x,y0=y;x=y0,y=x0-a/b*y0;}inline void work(){int lcm=1,ans=0;for (register int i=1;i<=n;++i)lcm*=b[i];for (register int i=1;i<=n;++i){int cnt=lcm/b[i];exgcd(cnt,b[i]);x=(x%b[i]+b[i])%b[i];ans=(ans+a[i]*x*cnt)%lcm;}printf("%d",(ans%lcm+lcm)%lcm);}int main(){n=read();for (register int i=1;i<=n;++i)a[i]=read();for (register int i=1;i<=n;++i)b[i]=read();work();return 0;}
题解:
由题意得:
化为同余式:
整理:
直接套用中国剩余定理即可。
//The code is from dxbdly#include<bits/stdc++.h>#define int long longusing namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n;int a[15],b[15];int x,y;inline int mul(int a,int b,int m)//此题需要用快速乘法加速{int ans=0;while (b){if (b&1)ans=(ans+a)%m;a=(a<<1)%m,b>>=1;}return ans;}inline void exgcd(int a,int b){if (b==0){x=1,y=0;return ;}exgcd(b,a%b);int x0=x,y0=y;x=y0,y=x0-a/b*y0;}inline int work(){int lcm=1,ans=0;for (register int i=1;i<=n;++i)lcm*=b[i];for (register int i=1;i<=n;++i){int cnt=lcm/b[i];exgcd(cnt,b[i]);x=(x%b[i]+b[i])%b[i];ans=(ans+mul(mul(a[i],x,lcm),cnt,lcm))%lcm;}printf("%lld",(ans%lcm+lcm)%lcm);}signed main(){n=read();for (register int i=1;i<=n;++i)a[i]=read();for (register int i=1;i<=n;++i)b[i]=read();for (register int i=1;i<=n;++i)a[i]=(a[i]%b[i]+b[i])%b[i];work();return 0;}
假设已经求出前个方程组成的同余方程组的一个解为
令:
则前个方程的通解为:
那么加入第n个式子,则问题变为:
求一个正整数使得
整理得:
而此式子可以通过拓展欧几里得求解。
那么我们就能求出前个式子的一个解:
同理处理前个式子,所以只要跑遍拓展欧几里得即可!
//The code is from dxbdly#include<bits/stdc++.h>#define int long longusing namespace std;inline int read(){int x=0;char c=getchar();bool f=0;while (!isdigit(c))f=f|(c=='-'),c=getchar();while (isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();return f?-x:x;}int n;int a[100005],b[100005];int x,y;inline int mul(int a,int b,int m){int ans=0;while (b){if (b&1)ans=(ans+a)%m;a=(a<<1)%m,b>>=1;}return ans;}inline int exgcd(int a,int b){if (!b){x=1,y=0;return a;}int gcd=exgcd(b,a%b);int x0=x,y0=y;x=y0,y=x0-a/b*y0;return gcd;}inline void work(){int lcm=b[1],ans=a[1];for (register int i=2;i<=n;++i){int cnt=(a[i]-ans%b[i]+b[i])%b[i],gcd=exgcd(lcm,b[i]),mod=b[i]/gcd;x=mul(x,cnt/gcd,mod);ans=ans+lcm*x,lcm*=mod;ans=(ans%lcm+lcm)%lcm;}printf("%lld",(ans%lcm+lcm)%lcm);}signed main(){n=read();for (register int i=1;i<=n;++i)b[i]=read(),a[i]=read();//注意是倒着读的。work();return 0;}