(sdau) Summary of the eleventh week.(数论)

数论基本概念:

一、数论基本概念

1、整除性
2、素数
a.素数与合数
b.素数判定
c.素数定理
d.素数筛选法

3、因数分解
a.算术基本定理
b.素数拆分
c.因子个数
d.因子和

4、最大公约数(GCD)和最小公倍数(LCM)
5、同余
a.模运算
b.快速幂取模
c.循环节

二、数论基本概念解析
1、整除性

若a和b都为整数,a整除b是指b是a的倍数,a是b的约数(因数、因子),记为a|b。整除的大部分性质都是显而易见的,为了阐述方便,我给这些性质都随便起了个名字。

i) 任意性,若a|b,则对于任意非零整数m,有am|bm。
ii) 传递性,若a|b,且b|c,则a|c。
iii) 可消性,若a|bc,且a和c互素(互素的概念下文会讲到),则a|b。
iv) 组合性,若c|a,且c|b,则对于任意整数m、n,有c|(ma+nb)。

2、素数

a.素数与合数

素数又称质数,素数首先满足条件是要大于等于2,并且除了1和它本身外,不能被其它任何自然数整除;其它的数称为合数;而1既非素数也非合数。

b.素数判定

如何判定一个数是否为素数?
i) 对n做[2, n)范围内的余数判定(C++中的’%'运算符),如果有至少一个数用n取余后为0,则表明n为合数;如果所有数都不能整除n,则n为素数,算法复杂度O(n)。
ii) 假设一个数能整除n,即a|n,那么n/a也必定能整除n,不妨设a <= n/a,则有a^2 <= n,即a <= sqrt(n)(sqrt表示对n开根号),所以在用i)的方法进行取余的时候,范围可以缩小到sqrt(n),所以算法复杂度降为O( sqrt(n) )。
iii) 如果n是合数,那么它必然有一个小于等于sqrt(n)的素因子,只需要对sqrt(n)内的素数进行测试即可,需要预处理求出sqrt(n)中的素数,假设该范围内素数的个数为s,那么复杂度降为O(s)。

c.素数定理

当x很大时,小于x的素数的个数近似等于x/ln(x),其中ln(x)表示x的自然对数,用极限表示如图一-2-1所示:
表示

d.素数筛选法

欧拉筛选法求z数

for(int i=2;i<=n;i++)
{
   if(!vis[i])
   prime[cnt++]=i;
   for(int j;;j++)
  {
      int x=i*prime[j];
      if(x>n)
         break;
      vis[x]=true;
      if(i%prime[j]==0)
         break;
  }
}

3、因数分解

a、算术基本定理

算术基本定理可以描述为:对于每个整数n,都可以唯一分解成素数的乘积
n=p1p2p3……pk (p1<=p2<=p3……<=pk)

b、素数拆分

给定一个数n,如何将它拆分成素数的乘积呢?

还是用到上面讲到的试除法,假设 n = pm 并且 m>1,其中p为素数,如果p > sqrt(n),那么根据算数基本定理,m中必定存在一个小于等于sqrt(n)的素数,所以我们不妨设p <= sqrt(n)。

然后通过枚举[2, sqrt(n)]的素数,如果能够找到一个素数p,使得n mod p == 0(mod 表示取余数、也称为模)。于是m = n/p,这时还需要注意一点,因为m中可能也有p这个素因子,所以如果p|m,需要继续试除,令m’ = m/p,直到将所有的素因子p除尽,统计除的次数e,于是我们得到了 n = (p^e) * n’,然后继续枚举素数对n’做同样的试除。

枚举完[2, sqrt(n)]的素数后,得到表达式如图一-3-3所示:
这时有两种情况:
i) S == 1,则素数分解完毕;
ii) S > 1, 根据算术基本定理,S 必定为素数,而且是大于sqrt(n)的素数,并且最多只有1个,这种情况同样适用于n本身就是素数的情况,这时n = S。

这样的分解方式称为因数分解,各个素因子可以用一个二元的结构体来存储。算法时间复杂度为O( s ),s为sqrt(n)内素数的个数。
c、因子个数

朴素的求因子个数的方法为枚举[1, n]的数进行余数判定,复杂度为O(n),这里加入一个小优化,如果m为n的因子,那么必然n/m也为n的因子,不妨设m <= n/m,则有m <= sqrt(n),所以只要枚举从[1, sqrt(n)]的因子然后计数即可,复杂度变为O(sqrt(n))。

d、因子和

4、最大公约数(GCD)和最小公倍数(LCM)

两个数a和b的最大公约数(Greatest Common Divisor)是指同时整除a和b的最大因数,记为gcd(a, b)。特殊的,当gcd(a, b) = 1,我们称a和b互素(上文谈到整除的时候略有提及)。

两个数a和b的最小公倍数(Leatest Common Multiple)是指同时被a和b整除的最小倍数,记为lcm(a, b)。特殊的,当a和b互素时,lcm(a, b) = ab。

gcd是基础数论中非常重要的概念,求解gcd一般采用辗转相除法,而求lcm需要先求gcd,然后通过lcm(a, b) = ab / gcd(a, b)求解。

5、同余

a、模运算
给定一个正整数p,任意一个整数n,一定存在等式n = kp + r; 其中k、r是整数,且满足0 <= r < p,称k为n除以p的商, r为n除以p的余数,表示成n % p = r (这里采用C++语法,%表示取模运算)。

对于正整数和整数a, b, 定义如下运算:

取模运算:a % p(a mod p),表示a除以p的余数。

模p加法:(a + b) % p = (a%p + b%p) % p

模p减法:(a - b) % p = (a%p - b%p) % p

模p乘法:(a * b) % p = ((a % p)*(b % p)) % p

幂模p : (a^b) % p = ((a % p)^b) % p

模运算满足结合律、交换律和分配律。

a≡b (mod n) 表示a和b模n同余,即a和b除以n的余数相等。

b、快速幂取模

幂取模常常用在RSA加密算法的加密和解密过程中,是指给定整数a,正整数n,以及非零整数p,求a^n % p。利用模p乘法,这个问题可以递归求解,即令f(n) = a^n%p,那么f(n-1) = a^(n-1)%p,f(n) = a*f(n-1) % p,这样就转化成了递归式。但是递归求解的时间复杂度为O(n),往往当n很大的时候就很难在规定时间内出解了。

当n为偶数时,我们可以将a^n%p拆成两部分,令b = a(n/2)%p,则an%p = b*b%p;

当n为奇数时,可以拆成三部分,令b = a(n/2)%p,则an%p = abb%p;

上述两个等式中的b可以通过递归计算,由于每次都是除2,所以时间复杂度是O(logn)。

c、循环节

例题:f[1] = a, f[2] = b, f[3] = c, 当n>3时 f[n] = (Af[n-1] + Bf[n-2] + C*f[n-3]) % 53,给定a, b, c, A, B, C,求f[n] (n < 2^31)。

由于n非常大,循环模拟求解肯定是不现实的,仔细观察可以发当n>3时,f[n]的值域为[0, 53),并且连续三个数f[n-1]、f[n-2]、f[n-3]一旦确定,那么f[n]也就确定了,而f[n-1]、f[n-2]、f[n-3]这三个数的组合数为535353种情况,那么对于一个下标k

并且在53 * 53*53次计算之内必定能够找到循环节,这个是显而易见的。

解决的问题:

A:

Everybody knows any number can be combined by the prime number.
Now, your task is telling me what position of the largest prime factor.
The position of prime 2 is 1, prime 3 is 2, and prime 5 is 3, etc.
Specially, LPF(1) = 0.
Input
Each line will contain one integer n(0 < n < 1000000).
Output
Output the LPF(n).
Sample Input
1
2
3
4
5
Sample Output
0
1
2
1
3

题目就是简单地求素数问题,如果不是素数也是由素数的倍数,找出对应素数的位数,输出即可。
没学数论之前肯定是要挨个穷举数字来计算,但是肯定会超时,因为数据范围有限,所以就运用素数筛选法,我们枚举0-1000000的数字,建立下标为1-1000000的数组,我们把下标为2的倍数的数相应的记为对应序号1,下标为三的倍数记为数字2,以此类推,这样就可以全部标记好所有的数了。
ac代码:

#include
#include
using namespace std;
int ans[1000000]={0};
int main()
{
   int n=1;
   for(int i=2;i<1000000;i++)
    {
        if(ans[i]==0)
        {
            ans[i]=n;
            for(int j=i+i;j<1000000;j+=i)
                ans[j]=n;
                n++;
        }
    }
   while (~scanf ("%d",&n))
		printf ("%d\n",ans[n]);

}

//刚开始第一次代码用的cin,cout然后是超时的,我还一直以为是做法问题,改了半天,后来才想起这个点,可能跟上程序设计课有关吧,用c++写的顺手了都,比赛的时候做的题总超时,就会记得这个点,但其实改了也不对,cf确实难得哟。

B:
A number whose only prime factors are 2,3,5 or 7 is called a humble number. The sequence 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 24, 25, 27, … shows the first 20 humble numbers.

Now given a humble number, please write a program to calculate the number of divisors about this humble number.For examle, 4 is a humble,and it have 3 divisors(1,2,4);12 have 6 divisors.
Input
The input consists of multiple test cases. Each test case consists of one humble number n,and n is in the range of 64-bits signed integer. Input is terminated by a value of zero for n.
Output
For each test case, output its divisor number, one line per case.
Sample Input
4
12
0
Sample Output
3
6

这个题呢,其实是运气做出来的吧,读了题就读出2,3,5,7,的数量的意思,就碰运气写了计算每个数字中含有2,3,5,7 个数来计算,最开始写的a,b,c,d是0,后面也是a+b+c+d;后来考虑到他本身所以abcd改成了1,因为运行不对就改成了乘法,完全凭感觉写代码,居然对了很惊喜,打比赛的时候,也有这种情况,读了题就会想到该怎么去做,就有时候很快就会做出来,但这种情况很少,我也就是打愚人节那场的时候碰巧过一次,一般是题目比较怪才有这种情况吧,不能靠运气啊。后面再继续总结打比赛情况,再简单讲个题。
ac代码:

#include
#include
using namespace std;
#define ll long long
int main()
{
    ll n,a,b,c,d;
    while (cin>>n&&n)
    {
        for(a=1; n%2==0; a++,n/=2);
        for(b=1; n%3==0; b++,n/=3);
        for(c=1; n%5==0; c++,n/=5);
        for(d=1; n%7==0; d++,n/=7);
        printf("%d\n",a*b*c*d);
    }
    return 0;
}

C:
七夕节那天,月老来到数字王国,他在城门上贴了一张告示,并且和数字王国的人们说:“你们想知道你们的另一半是谁吗?那就按照告示上的方法去找吧!”
人们纷纷来到告示前,都想知道谁才是自己的另一半.告示如下:
(sdau) Summary of the eleventh week.(数论)_第1张图片

数字N的因子就是所有比N小又能被N整除的所有正整数,如12的因子有1,2,3,4,6.
你想知道你的另一半吗?
Input
输入数据的第一行是一个数字T(1<=T<=500000),它表明测试数据的组数.然后是T组测试数据,每组测试数据只有一个数字N(1<=N<=500000).
Output
对于每组测试数据,请输出一个代表输入数据N的另一半的编号.
Sample Input
3
2
10
20
Sample Output
1
8
22
题目意思就是给出一个正整数n,求出不包含他本身的所有因子的和;

从1到根号n枚举即可,若是n的因子就加这个数,另外一个数不是这个数也加另外的这个数,有点绕口,一看代码就清楚了。我最开始还以为会超时呢,没想到过了,以为有啥别的套路呢还,想多了。

#include
using namespace std;
int main()
{
    int t,n;
    cin>>t;
    while(t--)
    {
        cin>>n;
        long long ans=1;
        for(int i=2;i*i<=n;i++)
        {
            if(n%i==0)
            {
                ans+=i;
                if(i!=n/i)
                    ans+=n/i;
            }
        }
        cout<<ans<<endl;
    }

}

近期总结:

讲一讲打cf的感觉吧,最近打的比较不顺,做题总是超时,昨天打的dv2只出了一个题,第一题卡在了第四个测试点很长时间,后边重新读浪费蛮多时间的,看题比较少,还是没做到过看题多,然后选择性的做题,基本都是a,b,c顺着来,一般半个小时做不出a题才会看b题,写代码改代码又浪费很多时间,最多也就看到过第四题,没啥长进呢咋,太难了,有的题解,第二天看,会有看不懂的代码片段,学的基础还是不够,很多用法没见过,复制代码去搜效率好低,很多回答的不是我想知道的。还是要多多积累吧。

你可能感兴趣的:(sdau程序竞赛周结记录)