距离集训结束还有6天左右吧……写一下博客总结一下这几天的学习。
首先第一天。上午是老师讲的数论,包括最基础的进制转换,判别回文数,
奇奇怪怪的取模运算,幂运算,线性筛数。gcd和lcm等等……
进制转换算是最基础的了吧,不会贴图就靠口糊了……首先我们最熟悉的数,也就是1,2,3,4,5,6,7,8,9,0,十个数,你可想过为什么只有0~9十个数的?是的,因为我们平时习惯了手上的十根手指,数着数着,就觉得,十进制是最适合的进制,这样不仅能使自己用的进制适合自己,更能用来区分,哦~有十根指头,那么又多了一个人。十进制就是逢十进一。比如一个数字1234,那么这个数字的每一位都是十的整次幂,我们按照从后往前标号,在4下标一个0,3下标一个1……那么这个数就可以表示成4*10^0+3*10^1+2*10^2+1*10^3(a^b为a的b次方),那么就可以同理推得各种进制数的表示法,比如一串二进制数1011,那么就是1*2^0+1*2^1+0*2^2+1*2^3=11(10进制),因为二进制是逢二进一,所以二进制就只有两个数,0和1啦。那么十进制和二进制如何转向呢?我们将十进制的一个数每次除以2取余数,直到所得到的商为0,如11,11/2=5……1,然后继续除,5/2=2……1,2/2=1……0,1/2=0……1,然后将各个余数从后往前写成一串,就是1011(2)啦,别的进制也是同理,就是需要注意,一般使用的进制最高一般为16进制,10用大写的a表示,11用大写的b表示,以此类推。为了不水……就贴个10进制转2进制的伪代码吧……(欢迎大佬纠错……)
void 10->2(int k){for (;k;k/=2)a[++t]=k%2;
for (;a[t]==0;t--); for (int i=t;i;--i)printf("%d",a[i]);}
判别回文数,不管是哪个进制下都是水题吧……以前刚开始学的时候,我是用两个数组存下两边分别的数然后进行比较的……但是后来发现其实没这么麻烦……对于比较小的数(一般接触的回文数都是小于long long 的吧……)可以直接用一个数存下,然后直接进行比较就可以了……回文数的话,大致意思就是左右对称的数吧……上个b进制下的回文数代码……(伪代码……知道意思即可……)
bool check(int k,int b){//b即为b进制,k为十进制下的该数
int s=0,m=k;
for (;k;k/=b) s=s*b+k%b;
return s==m;
}
因为自己懒得敲10进制以上的(说不定上面的可以判别)所以就这样啦……(10进制以上推荐使用数组存)。
取模运算应该大家都会吧……就和快速幂放在一起了……这里不单独写出了……(没有存模板的好习惯……都是写博客的时候手打的QAQ)
写几个特殊的
小学
数学定理吧……我们定义一个数为a
1、若2能整除a的最后一个数字,则a%2=0;
2、若4能整除a的最后两个数字,则a%4=0;
3、若8能整除a的最后三个数字,则a%8=0;
4、若3或9能整除a的各位数字之和,则a%3=0或a%9=0;
5、若5能整除a的最后一个数字,则a%5=0;
6、若25能整除a的最后两个数字,则a%25=0;
7、若125能整除a的最后三个数字,则a%125=0;
幂运算,下面的幂运算符号都用^表示。
幂运算,即为a^b=a*a*a*a*a……(b个a),不扯简单的……我们来讲解一下高级取模幂运算……
首先,手写的幂运算与C++中一个自带的pow相同,pow(a,b)即为a^b,但是一般来讲,我们要写的幂运算总是会超过long long范围的
(unsigned long long 和int128当然可能可行)一般来讲,会给我们一个取模的数,一般是个大素数,比如1e9+7。这时候我们要做的当然不可能是把整个数算出来再去%一个数,我们需要的就是自己写的快速幂。
const int N=1e9+7;
int fast_pow(int x,int k){//x为底,k为指数
int s=1;
for (;k;k>>=1,x=x*x%N)
if (k&1) s=x*k%N; //伪装成一行快速幂
return s%N;
}
//讲解一下,当一个数a^b%N时,我们先不管%N,a^b则为(a*a)^(b/2)
//对于这个数a*a我们进行取模,(显然)可以证明%N对之后的运算没有影响
//那么有疑问了,如果b为奇数怎么办呢?我们就将这个数拆成
//(a*a)^(b/2)*a,将这个多出来的a扔到答案s里即可。
然后讲解一下
线性筛数。
void seive( int Max )
{ memset( isPrime , true , sizeof( isPrime ));
isPrime[0 ] = false ; isPrime[1] = false ;
for ( int i = 2 ; i <= Max ; i++ )//遍历筛去所有最大因数是i的合数
{if ( isPrime[i] ) prime[ ++ total ] = i ;//把素数记录下来
//遍历已知素数表中比i的最小素因数小的素数,并筛去合数
for ( int j = 1 ; j <= total && i * prime[j] <= Max ; j++)
{ isPrime[ i * prime[j] ] = false ;
if (!( i % prime[j])) break;//找到i的最小素因数
}
}
}
他的基础思想为遇到一个数记录下,然后将这个数乘以2,3,4……直到这个数可以整除他乘的数后停止。比如得到一个6,将6*1标记下,然后6*2标记,发现6%2=0,退出。
gcd(最大公因数)和lcm(最小公倍数)
在讲这两个之前,我们得先贴一个定理,唯一分解定理:每个正整数都可以惟一地表示成素数的乘积,其中素数因子从小到大依次出现(这里的“乘积”可以有0个、1个或多个素因子)。那么我们依然可以用显然来证明。
依靠这个定理,我们可以发现,求一个数的gcd可以将时间复杂度降低到O(log n),也就是常说的辗转相除法。
int gcd(int x,int y){
return !y?x:(y,x%y);
}//一行gcd,因为压行太丑了……
int gcd(int x,int y){
if (y==0) return x;
else gcd(y,x%y);
}
那么计算lcm只需要将x/gcd(x,y)*y/gcd(x,y)*gcd(x,y)即可。
咳咳,那么下面开始进入正文(并不),上面的纯依靠瞎BB,下面则是几个数论的定理(
其实我自己也不知道怎么证明并且我自己也没怎么搞明白)。
首先是
威尔逊定理,
对于一个素数p,(p-1)!≡-1(mod p),威尔逊定理的逆定理也成立,即对于一个数,如果(p-1)!≡-1(mod p),则p为素数。(体谅一下作者在搜狗输入法的符号里找了半天同余符号QAQ)。当然,依然可以用显然证明。
证明。
那就敲个大致上算是证明的代码吧……
事实是我都不知道这个定理到底有什么用
void Wilson(int p){
int P=p;
int s=1;
for (;--p;s=s*p%P);
return s==(P-1);
}
费马小定理,
若p为质数,且gcd(p,a)=1,则(a^(p-1))%p=1,也写作a^(p-1)≡1(mod p)。
这两个公式非常有用大家要牢记。
说实话到现在这两个公式除了看看我都不知道能干啥用
欧拉定理,若n,a为正整数,且gcd(n,a)=1,则a^(φn)≡1(mod n)。
φn的意义为,从1~n之内所有质数(素数)的个数,证明就交给百度百科吧……
证明。
在这里贴个欧拉函数求法,百度百科大佬的程序……
/*
特性 :
1.若a为质数,phi[a]=a-1;
2.若a为质数,b mod a=0,phi[a*b]=phi[b]*a
3.若a,b互质,phi[a*b]=phi[a]*phi[b](当a为质数时,if b mod a!=0 ,phi[a*b]=phi[a]*phi[b])
*/
int m[n],phi[n],p[n],nump;
//m[i]标记i是否为素数,0为素数,1不为素数;p是存放素数的数组;nump是当前素数个数;phi[i]为欧拉函数
int make()
{
phi[1]=1;
for (int i=2;i<=n;i++)
{
if (!m[i])//i为素数
{
p[++nump]=i;//将i加入素数数组p中
phi[i]=i-1;//因为i是素数,由特性得知
}
for (int j=1;j<=nump&&p[j]*i<n;j++) //用当前已的到的素数数组p筛,筛去p[j]*i
{
m[p[j]*i]=1;//可以确定i*p[j]不是素数
if (i%p[j]==0) //看p[j]是否是i的约数,因为素数p[j],等于判断i和p[j]是否互质
{
phi[p[j]*i]=phi[i]*p[j]; //特性2
break;
}
else phi[p[j]*i]=phi[i]*(p[j]-1); //互质,特性3其,p[j]-1就是phi[p[j]]
}
}
}
随便放几道题……可以去洛谷上做一做……luoguP1126,P1096,P1134。
那么这几天的数论总结就到这里……说实话没听懂欧几里得拓展和中国剩余……所以可能得下次写了……