信息学中的数论(二)

注:本文章中大多数定理未证明,一因为太过繁琐,二因为我太菜了不会证。

希望得到证明的就请舍弃这篇吧。

数论中有一个东西非常常见,也非常烦。

这个东西叫“质数”

(及其相关知识)

那么这一篇来聊聊与质数有关的话题吧:

质数,筛质数的方法,phi函数,线性筛。

恩先说质数,

质数就是只有1和它本身两个因数的自然数。

与质数相对的就是合数

好了质数讲完了。

筛质数的方法?

判断一个数是否是质数大家都会吧,根号的做法

如果要筛出1到n之间所有的质数呢?

普通的筛法求质数,我们来归纳一下,可以通过筛去合数的方法来筛质数。

如下代码:

for(int i=2;i<=n;++i)
    for(int j=i+i;j<=n;j+=i)
        check[j]=1;

我们来分析一下该算法的复杂度:

n/2+n/3+......+n/n = n*(1/2+1/3+......+1/n)

右边那个括号里面的,是调和级数

有某个结论:1+1/2+......+1/n = ln(n+1)+r

那么我们把时间复杂度近似地看做为nlogn

这种做法在n=10000000的时候显然效率不高。

有什么快速的筛法呢?

请继续阅读,之后的“线性筛”会来讲解。

phi函数。

原来是这么写的:“φ”

φ(n)(欧拉函数)表示,小于n的正整数中,与n互质的数的个数,

特别的,φ(1)=1。

由于这个符号比较难打,之后会用phi代替。

这个东西非常有用。

大家也许还记得这个东西:

p为质数时,a^(p-1)%p = 1 (费马小定理)

先告诉你一个非常响亮的结论:

对于互质的正整数a和n,有a^phi(n) % n = 1(欧拉定理)

由于质数p,phi(p)=p-1,

因此费马小定理是欧拉定理的一个特例。

我们来看一下phi函数的一些特性:

①,p为质数时,phi(p)=p-1

②,p为质数,n为正整数时,phi(p^n)=p^(p-1)*(p-1)

③,a,b互质时,phi(a*b)=phi(a)*phi(b) (即积性函数)

那么根据以上性质,也许大家能轻易得到phi函数的一个通式:

p1,p2,......,pn是x的不同的质因子。

恩接下来的东西非常重要,

其实线性筛是个不难的算法,却是个非常优秀的算法;

好了终于要讲线性筛了

我们先来讲线性筛质数

普通筛法求质数是通过筛合数的方法来得到质数,

那能不能把时间复杂度优化到O(n)呢?

这就是线性筛的美妙之处。

假如一个合数90

普通的筛法会使90被其所有的因数筛去,较慢,

我们能否做到,该合数只会被它最小的质因数2筛去,而不是被其他数筛去呢?

如果能做到,那么每个合数被筛去的复杂度是O(1)的,总时间复杂度也就是O(n)的了

我们来看一下代码:

const int N=10000000;
int pn,prime[N+1];        //pn表示质数的个数,prime数组存放质数
bool check[N+1];          //check[i]表示i是否为合数
void getprime()
{
    for(int i=2;i<=N;++i)
    {
        if(!check[i])
        {
            ++pn; p[pn]=i;          //如果i不是合数,加入质数数组
        }
        for(int j=1;j<=pn;++j)
        {
            if(i*p[j]>N)break;      //筛去1到N之间的合数
            check[i*p[j]]=1;        //将合数i*p[j]标记
            if(i%p[j]==0)break;     //使得合数只会被其最小的质因数筛去。
        }
    }
}

那么,如何线性筛phi呢?

我们现在需要做的是,用O(n)的时间复杂度,求出phi(1),phi(2),......,phi(n)

首先,来回顾一下phi的性质:

1、积性函数性质

2、x>=1时,phi(p^x)=p^(x-1)*(p-1)

那么,我们能否在线性筛素数的基础上,顺便求phi呢?

对于s是质数次方的情况,可以通过性质2轻松解决,

对于s是合数的情况:

很简单,如果s = p1^s1 * p2^s2 * ...... * pm^sm,p1<p2<...<pm,

那么,根据积性函数性质,我们可以通过phi(s / (p1^s1) ) 求得phi(s)

易得phi(s) = phi( s / (p1^s1) ) * phi(p1 ^ s1)

那么一种做法是,开一个数组f,f[s]=s/(p1^s1) (即把最小质因子全部去除之后的数)

这样,如果s1==1,f[s]=s/p1;否则f[s] = f[s/p1]。(相当于s只会被p1筛去,通过线性筛可以实现)

于是phi(s) = phi(f[s]) * phi(s/f[s])

只要通过线性筛求f数组就可以了。

代码如何实现,

留作思考题(其实是我lazy)

时间: 2024-10-24 13:36:37

信息学中的数论(二)的相关文章

信息学中的数论(一)

做oi题目的时候,遇到数论题会令我兴奋不已. 这一篇让我来聊一聊我学过的gcd,lcm,扩展欧几里得算法,逆元,组合数等. 这篇贴的代码都是未经过编译运行的,所以如果有错或有疑问请评论. 恩 那么什么是数论 和数学有关的非几何都是数论? 嘛,我也不知道定义,那么就草率地认为所有和数学有关的非计算几何知识都是数论吧. 我们先来聊一聊gcd. 这个东西,非常的有用. 它的名字叫最大公约数. 正常人都知道,有一个方法叫辗转相除法(证明略): int gcd(int a,int b) { if(!b)r

数论二&#183;Eular质数筛法

#1295 : 数论二·Eular质数筛法 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho:小Hi,上次我学会了如何检测一个数是否是质数.于是我又有了一个新的问题,我如何去快速得求解[1,N]这个区间内素数的个数呢? 小Hi:你自己有什么想法么? 小Ho:有!我一开始的想法是,自然我们已经知道了如何快速判定一个数是否是质数,那么我就直接将[1,N]之间每一个数判定一次,就可以得到结果.但我发现这个方法太笨了. 小Hi:确实呢,虽然我们已经通过快速素数检测将每

二叉树进阶之寻找一棵二叉树中的最大二叉搜索子树

转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6618915.html  (规律:在二叉树中寻找某性质的,都应该以递归思维:从根结点开始递归左右,一直到底,由底向上返回的信息来判断当前结点.求当前结点.即:二叉树的题目,从下往上想,递归的返回过程就是从下往上由叶到根建立二叉树的过程,在此过程中对每一步的"根"结点作性质判断,返回到根时即是整棵树的性质判断了) 从一棵树中寻找结点数最多的二叉搜索子树,并返回这棵子树的头结点. 从题目我们知道以下要求

Jquery:Jquery中的事件&lt;二&gt;

这几天快忙死了,办了离职还得办入职,完全打乱了我的计划,但是能有一个理想的工作,还是很开心的,以后加把劲,争取把计划再赶上来!不说了,学习!!! 五.事件对象的属性 1.event.type:获取事件的类型,其中event是事件的对象. 2.event.preventDefaule(),在上一个学习笔记中已经有介绍了,该方法是阻止默认的事件事件行为.event.stopPropagation(),该方法的作业是阻止事件的冒泡. 3.event.target,它的作用是获取到触发事件的元素.通过返

掌握JS中的“this” (二)

在上一篇文章 掌握JS中的"this" (一) 里面, 我们学会了如何正确使用JavaScript中的 this 关键字及其基本原理.我们也知道决定 this 指向哪个对象的关键因素, 是找出当前的执行上下文(execution context).但如果执行上下文不按正常的方式进行设置,问题可能就会变得很棘手.在本文中,我会着重提示在哪些地方会发生这种情况, 以及用什么方式可以弥补. 解决常见问题 在本节中,我们将探讨一些使用 this 关键字时最常见的问题, 并了解如何处理这种情况.

19.把1~100存到二维数组a[10][10]中,并按二维矩阵形式输出

#include<iostream>using namespace std; int main(){    int a[10][10];    for(int i=0;i<10;i++)    {        for(int j=0;j<10;j++)        {            a[i][j]=i*10+j+1;//二维数组逻辑上还是一维数组的存储方式        }    }    for(int j=0;j<10;j++)    {        for

Golang中使用log(二):Golang 标准库log的实现

前一篇文章我们看到了Golang标准库中log模块的使用,那么它是如何实现的呢?下面我从log.Logger开始逐步分析其实现. 其源码可以参考官方地址 1.Logger结构 首先来看下类型Logger的定义: type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix to write at beginning of each

C++中的异常处理(二)

C++中的异常处理(二) 标签: c++C++异常处理 2012-11-24 20:56 1713人阅读 评论(2) 收藏 举报  分类: C++编程语言(24)  版权声明:本文为博主原创文章,未经博主允许不得转载. 先看下面的代码: [cpp] view plain copy int main() { int *i=new int(10); /* 这中间的代码出现异常 */ delete i; return 0; } 如果出现了这样的情况,动态分配的内存就不会被释放.为了处理这样的问题,可以

【编程题目】求一个矩阵中最大的二维矩阵(元素和最大)

35.(矩阵)求一个矩阵中最大的二维矩阵(元素和最大).如:1 2 0 3 42 3 4 5 11 1 5 3 0中最大的是:4 55 3要求:(1)写出算法;(2)分析时间复杂度;(3)用 C 写出关键代码 早上灭小题! /* 35.(矩阵) 求一个矩阵中最大的二维矩阵(元素和最大).如: 1 2 0 3 4 2 3 4 5 1 1 1 5 3 0 中最大的是: 4 5 5 3 要求:(1)写出算法;(2)分析时间复杂度;(3)用 C 写出关键代码 */ #include <stdio.h>