只是想系统的学一下数论而已orz于是打算写这么一个系列
参考的书目是《信息学奥赛之数学一本通》,不过这书特别的坑所以大概只会选择其中一部分来说说(应付NOIP就行了在意那么多做什么hhh)
此外会参考前几日培训时zhx的讲课内容以及P2O5dalao的笔记。至于剩余的参考内容等,到了涉及的时候再说吧。
(I)整除
整除是数论的重要基础。
一、整除的定义与符号表示
设a为非零整数,b为整数。若 ∃ q∈Z,满足b=a·q,则称b可被a整除或a整除b,记作a|b,同时b是a的倍数,a是b的因子。
二、整除的基本性质
(1)a|b,b|c
⇒
a|c
(2)a|b,a|c
⇔∀x,y∈Z,a|(b×x+c×y)
(3)
若m≠0,则a|b⇔(m⋅a)|(m⋅b)
(4)
x,y∈Z,且∃a,b使ax+by=1,a|n,b|n⇒(a⋅b)|n
证明:
∵a|n,b|n
∴ab|bn,ab|an(性质3)
∴ab|(anx+bny)
∴ab|n
Q.E.D
(5)b=qd+c,则d|b ⇔ d|c
三、一些特殊的倍数性质(剪枝什么的最有爱了)
2的倍数 不用说吧,其实a&1什么的也够快了
3的倍数 同样不用说
5的倍数 末位5或0
11的倍数 奇数位与偶数位之差
7、11、13的倍数 末三位与前几位之差是7/11/13倍数(可以递归多幸福)
2n
的倍数 后n位是
2n
的倍数
四、一道例题
有一个密码箱,0~n-1中某些整数是它的密码,且满足:如果a,b是它的密码,那么(a+b)%n是它的密码。某人试了k次密码,前k-1次均失败,第k次成功了。问该密码箱最多有多少不同密码。
输入:n,k,试的序列
输出:结果
范围:1
≤
k
≤
250000,k
≤
n
≤1014
显然我们没有什么思路,就需要数学推理。
引理 不定方程ax+by=c(
a,b,c∈N
)有整数解的充要条件是gcd(a,b)|c,且解为
gcd(a,b)表示a,b最大公因数,证明各位可以自己思考(其实我不会)
不妨设x为密码,x·k-n·c=gcd(x,n),该方程若有整数解则gcd(x,n)|gcd(x,n),显然成立。故存在k使得x·k%n==gcd(x,n)。
再来看题意,由于x为密码,(x+x)%n就位密码,从而,(2x%n+x)%n为密码。以此类推,x·k%n为密码,即gcd(x,n)为密码。
假设x,y为密码,由题意(px+cy)%n为密码,即ax+by=gcd(x,y)有解。从而,ax+by ≡ gcd(x,y)有解,然后根据一系列复杂的推理(我也很绝望啊,完全看不懂QWQ),我们得出gcd(x,y)为解。
那么我们就可以求出一个最小的x,其余密码集合所有数都是它的倍数,而求出x的方法就是先枚举出所给密码a[k]的所有因数,然后通过划去所有gcd(a[i],a[k]),找出目标的x,答案为n/x。
嗯……不知道各位看懂没有,反正我没有。但做法记住就好,实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n,k,cnt,a[250005],q[250005];
bool flag[250005];
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int main(){
scanf("%I64d%I64d",&n,&k);
for(int i=1;i<=k;++i){
scanf("%I64d",&a[i]);
}
a[k]=gcd(a[k],n);
for(int i=1;i<=k;++i){
a[i]=gcd(a[i],a[k]);
}
for(LL i=1;i*i<=a[k];++i){//处理所有因数
if(a[k]%i==0){
q[++cnt]=i;
if(i*i!=a[k])
q[++cnt]=a[k]/i;
}
}
sort(q+1,q+cnt+1);
for(int i=1;i<k;++i)//枚举出所有q中不正确的因子
flag[lower_bound(q+1,q+cnt+1,a[i])-q]=1;
for(int i=1;i<=cnt;++i){//将这些不正确因子的因子去除
if(f[i]){
for(int j=1;j<i;++j)
if(a[i]%a[j]==0)
flag[j]=1;
}
}
for(ans=1;f[ans];++ans);//找出最小符合条件的x
printf("%d\n",n/q[ans]);
return 0;
}
(II)同余
一、什么是同余
如果a%m=b%m,则称a,b同余,记作
a≡b
(mod m)
(我就不放标准数学定义咬我啊)
二、同余的基本性质
1、a
≡
a(mod k)
2、a
≡
b(mod k)
⇒
b
≡
a(mod k)
3、a
≡
b(mod k),b
≡
c(mod k)
⇒
a
≡
c(mod k)
4、a
≡
b(mod k)
⇒
a+c
≡
b+c(mod k)
5、a
≡
b(mod k),c
≡
d(mod k)
⇒
ac
≡
bd(mod k)
6、a
≡
b(mod k)
⇒an≡bn
(mod k)
推论
1、a·b%k=(a%k)·(b%k)%k
2、a%p=x,a%q=x,且gcd(p,q)=1,则a%(p·q)=x
注意:
a
≡
b(mod k)不一定有
⌊an⌋≡⌊bn⌋
(mod k)成立
三、重要模板:快速幂+取模
必背模板,频率挺高的。
该模板的用处在于:在O(logn)的时间内求出
ap%k
的值。
我们的做法是:将每一步分解。例如,
389
=
(((((32)2⋅3)2⋅3)2)2)2⋅3
,没错就类似于秦九韶算法,在每次乘法时加一次取模运算即可实现。
代码(zhx Ver.):
int ksm(int a,int x,int mod)
{
ans=1;
while(x)
{
if (x&1) ans=(ans*a) % mod;//x为奇数
A=(a*a) % mod;
x>>1;//X除以2
}
}
四、仍然是一道问题
(POJ3292)
设集合H{x|x=4k+1,k
∈N
},如果m
∈
H,且m能且只能分解为两个合数,且这两个合数只能被分解为两个素数,则称m为H-合成数。
例如,441=21*21=9*49,其中21=3*7,9=3*3,49=7*7.
输入n行,每行有一个整数pi,要求0-pi间H-合成数的个数。
首先我们先将所有H素数筛出来。H数有一个性质:i为H素数,则5i+4i*k是H数但不是H素数。因为显然(5i+4ik)%4=5i%4=(5%4)*(i%4)%4=1.且该数因为有因子i,一定是合数。
以此可以方便的筛出Hprime,然后再讨论即可。实现:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000005
bool iHpr[MAXN],iHse[MAXN];
int Hpr[MAXN],acc[MAXN],n;
int main(){
for(int i=5;i<MAXN;i+=4){//筛H素数与可分解H素数之积的H合数
if(iHpr[i]) continue;
Hpr[++n]=i;
for(int k=i*5;k<MAXN;k+=4*i)
iHpr[k]=true;
}
for(int i=1;i<=n;++i)//求H合成数
for(int j=1;j<=i&&Hpr[i]*Hpr[j]<MAXN;++j)
iHse[Hpr[i]*Hpr[j]]=true;
for(int i=1;i<MAXN;++i)//递推结果
acc[i]=acc[i-1]+iHse[i];
int h;
cin>>h;
while(h!=0)
cout<<acc[h],cin>>h;
return 0;
}
略有些懵逼的话可以输出一下Hpr这个数组看看。