目录
一些定义
- 倍数因数整除 (a|b) a是b的因数
- 最大公因数(a,b)
- 最小公倍数 [a,b]
- 互质,最大公因数为1 互质结论 a与b互质时,若a|br 则 a|r
欧几里得算法
__gcd(a,b)== __gcd(b,a%b) (a%b==0时,return a)
int gcd(int a,int b)
{
return b==0? a:__gcd(b,a%b);
}
例题 天平
aL=bR
二者同时除以__gcd(a,b)
a/__gcd(a,b) L=b/__gcd(a,b) R
红色互质
L的存在导致
a/__gcd(a,b) | b/__gcd(a,b) R
进而
a/__gcd(a,b) | R
进而,R最小时是a/__gcd(a,b)
同理可求得L最小值
证毕
扩展欧几里得算法
若a,b,不全为0,存在x,y整数,使得 ax+by=(a,b)
设 a>b
推理1 显然b=0时,__gcd(a,b)=a 此时 x=1,y=0
推理2 ab!=0时
ax1+by1=gcd(a,b)
根据欧几里得算法,代换掉a,b,gcd(a,b)
bx2+(a%b)y2=gcd(b,a%b)
进而
ax1+by1=bx2+(a%b)y2
=bx2 +( a-(a/b)*b )y2
=bx2 + ay2 -(a/b)*b y2 红色为常数
故 x1=y2 y1=x2-(a/b)*y2
即递归不断递推时,一旦遇到b=0,立马返回,回溯时,不断更新前面的值,直到找到一组解
int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int t=y; //更深层递归结束时,修改了y也就是y2
y=x-(a/b)*y; // y1=x2-(a/b)*y2
x=t; //x1=y2
return r; //普通欧几里得算法
}
注意我们求出的x,y是一组任意解,但解的个数是无穷的
设求出x0,y0
( x0+k * b/(a,b) , y0- k * a/ (a,b) )
a(x0+k * b/(a,b))+b (y0- k * a/ (a,b) )=(a,b) 展开相消即可
例题 青蛙的约会(POJ 1061)
设跳了 u次
x+um=y+un+ vL
u(m-n) - vL = y-x
u(n-m)+vL=x-y
此时我们发现,b就是L,为整数,而a是可能为负数的,而gcd对非负整数才有意义,故此时等式两边取负 a,c变成了相反数,b不去改变,可理解为v自动变号
扩欧求出一组u,v特解
u a + v b =exgcd
我们知道x= x0+ k * b/(a,b) ,而借助取模乘加运算得到正数的性质
任意的x = xmin + k*b/(a,b)
即( x mod b/(a,b) + b/(a,b) ) mod ( b/(a,b) ) = xmin
这样我们便对 u a + v b =exgcd 求出一组最小正整数解
但还要注意的是
我们要求的是
u(n-m)+vL=x-y 即 u a + v b= x-y 故再乘上 x-y/exgcd即可
还有一个细节是判断无解,由于我们扩展欧几里得求出的是欧几里得算法得到的gcd,而要满足原方程组有解,必须保证c是gcd的整数倍,这样才能在系数上扩大若干倍得到原方程解
#include <iostream>
# include<cstring>
# include<algorithm>
# define mod 998244353
using namespace std;
typedef long long int ll;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(!b)
{
x=1;
y=0;
return a;
}
ll r=exgcd(b,a%b,x,y);
ll t=y; //更深层递归结束时,修改了y也就是y2
y=x-(a/b)*y; // y1=x2-(a/b)*y2
x=t; //x1=y2
return r; //普通欧几里得算法
}
int main()
{
ll n,m,x,y,l;
cin>>x>>y>>m>>n>>l;
ll a=n-m;
ll b=l;
ll c=x-y;
if(a<0)
{
a=-a;
c=-c;
}
ll x1=0,y1=0;
ll exg=exgcd(a,b,x1,y1);
if(c%exg)
{
cout<<"Impossible";
return 0;
}
else
{
ll mo= b/exg;
cout<<( (x1 *(c/exg) )%mo+ mo)%mo;
}
return 0;
}
埃氏筛法
核心思想,筛掉质数的倍数
以下为优化后的埃氏筛,复杂度接近On,值得注意的是,我们仅仅利用sqrt(n)以内的素数就可以筛掉全部n以内的合数。这一特性常常用来进行区间筛法。
#include <iostream>
# include<cstring>
# include<algorithm>
# define mod 998244353
using namespace std;
typedef long long int ll;
bool not_prime[10000000];
int prime[1000000],tot=0;
int main()
{
int n;
cin>>n;
for(int i=2;i*i<=n;i++)
{
if(!not_prime[i])
{
prime[++tot]=i;
for(int j=i*i;j<=n;j+=i)
{
not_prime[j]=1;
}
}
}
return 0;
}
区间筛
在大数范围内挑选一个长度较长但不至于过长的区间,要求筛选出其中的质数。
根据埃氏筛核心思想,先预处理出大数范围内能够筛选出全部合数的质数数组,再将这些质数进行小区间的筛选。方法与埃氏筛一样,只是布尔数组的下标1代表L,其余各下标都进行了平移。为了精确筛选,我们直接定位在第一个大于等于L的prime[i]的若干倍上,根据数论知识,这个点((L+prime[i])/prime[i]))*prime[i] ;
例题
Prime Distance |
#include <iostream>
# include<cstring>
# include<algorithm>
# include<math.h>
# define mod 998244353
using namespace std;
typedef long long int ll;
bool not_prime[10000000];
int prime[1000000],tot=0;
int pprime[1000000];
bool not_prime1[1000000+10];
int main()
{
for(ll i=2;i*i<=2147483647;i++)
{
if(!not_prime[i])
{
prime[++tot]=i;
for(ll j=i*i;j*j<=2147483647;j+=i)
not_prime[i]=1;
}
}
int t;
cin>>t;
while(t--)
{
ll L,R;
cin>>L>>R;
if(L==1)
L++;
memset(not_prime1,0,sizeof(not_prime1));
for(ll i=1;i<=tot&&prime[i]*prime[i]<=R;i++)
{
for(ll j=(L+prime[i])/prime[i]*prime[i];j<=R;j+=prime[i])
{
not_prime1[j-L+1]=1;
}
}
int cnt=0;
for(ll i=1;i<=R-L+1;i++)
{
if(!not_prime1[i])
pprime[++cnt]=i+L-1;
}
if(cnt<2)
{
cout<<"There are no adjacent primes."<<endl;
continue;
}
ll Min=0x3f3f3f3f,Max=-1;
ll A,B,C,D;
for(int i=2;i<=cnt;i++)
{
if(pprime[i] - pprime[i - 1] < Min) Min = pprime[i] - pprime[i - 1], A = pprime[i - 1], B = pprime[i];
if(pprime[i] - pprime[i - 1] > Max) Max = pprime[i] - pprime[i - 1], C = pprime[i - 1], D = pprime[i];
}
printf("%lld,%lld are closest, %lld,%lld are most distant.\n",A,B,C,D);
}
return 0;
}
欧拉筛
每个合数只需要被其最小的质因子筛掉
on遍历时,若是质数加入质数数组,无论质数与否,都参与筛选,在合理范围内,筛掉本身与所以质数的乘积,一旦可以整除某个质数,终止循环
#include <iostream>
# include<cstring>
# include<algorithm>
# define mod 998244353
using namespace std;
typedef long long int ll;
bool not_prime[10000000+10];
int prime[1000000+10],tot=0;
int main()
{
int n;
cin>>n;
for(int i=2;i<=n;i++)
{
if(!not_prime[i])
{
prime[++tot]=i;
}
for(int j=1;j<=tot&&i*prime[j]<=n;j++)
{
not_prime[prime[j]*i]=1;
if(i%prime[j]==0)
break;
}
}
for(int i=1;i<=tot;i++)
{
cout<<prime[i]<<" ";
}
return 0;
}
例题
Sum of Consecutive Prime Numbers |
先用筛法筛出全部素数,再利用尺取法进行暴力枚举即可
#include <iostream>
# include<cstring>
# include<map>
# include<algorithm>
# define mod 998244353
using namespace std;
typedef long long int ll;
bool not_prime[40000000+10];
int prime[40000000];
int tot=0;
int main()
{
for(int i=2;i<=4*10000000;i++)
{
if(!not_prime[i])
{
prime[++tot]=i;
}
for(int j=1;(ll)i*prime[j]<=4*10000000&&j<=tot;j++)
{
not_prime[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
int ans=0;
int left=1,right=1;
ll tempsum=0;
while(1)
{
while(tempsum+prime[right]<=n&&right<=tot)
{
tempsum+=prime[right];
right++;
}
if(tempsum==0)
break;
if(tempsum==n)
ans++;
tempsum-=prime[left];
left++;
}
cout<<ans<<endl;
}
return 0;
}
质因数分解
#include <iostream>
# include<cstring>
# include<algorithm>
# include<math.h>
# define mod 998244353
using namespace std;
typedef long long int ll;
int main()
{
int n;
cin>>n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
cout<<i<<" ";
while(n%i==0)
n/=i;
}
}
if(n>1)
cout<<n<<" ";
return 0;
}
例题
Prime Land |
#include <iostream>
# include<cstring>
# include<map>
# include<algorithm>
# include<math.h>
# define mod 998244353
using namespace std;
typedef long long int ll;
map<int,int>m;
int main()
{
int t;
cin>>t;
while(t--)
{
int k;
cin>>k;
int n=1;
m.clear();
for(int i=1; i<=k; i++)
{
int x,y;
cin>>x>>y;
while(y--)
n*=x;
}
n--;
for(int i=2; i*i<=n; i++)
{
int cnt=0;
if(n%i==0)
{
while(n%i==0)
{
n/=i;
cnt++;
}
m[i]=cnt;
}
}
if(n>1)
{
m[n]++;
}
for(auto it=m.rbegin(); it!=m.rend(); it++)
cout<<it->first<<" "<<it->second<<" ";
cout<<endl;
}
return 0;
}
Stein算法
大整数GCD
a=0 || b=0 (0,b)=b,(a,0)=a
a,b皆偶 (a,b)=(a/2,b/2)
一偶一奇 (a,b)=(a/2,b) // (a,b/2) 偶数除以二
a,b皆奇数 (a,b) = (b,a-b) 其中a>b