文章标题 数论线性筛总结 (素数筛,欧拉函数筛,莫比乌斯函数筛,前n个数的约数个数筛)

来源:互联网 发布:淘宝有降价通知吗 编辑:程序博客网 时间:2024/05/16 07:58

转自 http://blog.csdn.net/tc_to_top/article/details/48025849

线性筛

线性筛在数论中起着至关重要的作用,可以大大降低求解一些问题的时间复杂度,使用线性筛有个前提(除了素数筛)所求函数必须是数论上定义的积性函数,即对于正整数n的一个算术函数 f(n),若f(1)=1,且当a,b互质时f(ab)=f(a)f(b),在数论上就称它为积性函数,若a,b不互质也满足的话则称作完全积性函数,下面说明每个筛子是怎么筛的。
最基础的是素数筛,其它三个筛都是以素数筛为前提

素数筛

void get_prime()    {        int pnum = 0;         for(int i = 2; i < MAX; i++)        {            if(!noprime[i])                p[pnum ++] = i;            for(int j = 0; j < pnum && i * p[j] < MAX; j++)            {                noprime[i * p[j]] = true;              if(i % p[j] == 0)                    break;            }        }    }  

主要是break那里,比如12这个数,在普通筛的时候12要被2和3都筛一次,显然这种多余的操作会增加时间复杂度,线性筛中一个数字只被它最小的素因子筛掉,12只被2筛掉,当i等于6的时候2 * 6==12筛掉12,这时候6%2==0可以break了,如果不break,那么6还会把18筛掉,此时是通过6 * 3来筛掉18,可是显然18最小的素因子是2,所以当i枚举到9的时候有9*2==18,这样18就又被筛了一次,因此在i等于6的时候不用拿6去筛18,下面用公式来说明:
当p[j]是i的因子时,设i=p[j] * k,因为素因子从小到大枚举,所以p[j]是i的最小素因子,此时i已经无需再去剔除p[j’] * i (j’> j) 形式的合数了,因为p[j’] * i 可以写成p[j’] * (p[j] * k)=p[j] * (p[j’] * k),也就是说所有的p[j’] * i 将会被将来的某个i’=p[j’]*k剔除掉,当前的i已经不需要了。

欧拉函数筛

欧拉函数phi[i]表示的是与1-i中与i互质的数的个数。
求解时分三种情况:
1.当i为素数时,显然phi[i] = i - 1
2.当i % p[j] != 0时,gcd(i, p[j]) = 1,由积性函数的性质可得phi[i * p[j]] = phi[i] * phi[p[j]] = phi[i] * (p[j] - 1) (p数组表示素数)
3.当i % p[j] ==0时,根据欧拉函数的求法:phi[n] = n * ∏(1 - 1/p),p为n的质因子,故若i % p[j] == 0,i * p[j]的质因子数不变
则phi[i * p[j]] = i * p[j] * ∏(1 - 1/p) = p[j] * i * ∏(1 - 1/p) = p[j] * phi[i]
由此得到欧拉函数筛

void get_eular()    {        pnum = 0;      for(int i = 2; i < MAX; i++)        {            if(!noprime[i])            {                p[pnum ++] = i;                phi[i] = i - 1;            }            for(int j = 0; j < pnum && i * p[j] < MAX; j++)            {                noprime[i * p[j]] = true;                if(i % p[j] == 0)                {                    phi[i * p[j]] = phi[i] * p[j];                    break;                }                phi[i * p[j]] = phi[i] * (p[j] - 1);            }        }    }   

莫比乌斯函数筛

莫比乌斯函数mob[i]
若i为奇数个不同素数之积mob[i] = -1
若i为偶数个不同素数之积mob[i] = 1
若i有平方因子则mob[i] = 0。
这个做起来比欧拉函数容易,在素数筛上,若i为素数则mob[i] = -1,若i % p[j] == 0,则mob[i * p[j]] = 0,显然p[j]就是它的平方因子,否则mob[i * p[j]] = -mob[i]
由此得到莫比乌斯函数筛:

void Mobius()  {      int pnum = 0;      mob[1] = 1;      for(int i = 2; i < MAX; i++)      {          if(noprime[i])          {              p[pnum ++] = i;              mob[i] = -1;          }          for(int j = 0; j < pnum && i * p[j] < MAX; j++)          {              noprime[i * p[j]] = false;              if(i % p[j] == 0)              {                  mob[i * p[j]] = 0;                  break;              }              mob[i * p[j]] = -mob[i];          }      }  }  

前n个数的约数个数筛

facnum[i]表示i的约数个数
通过素数筛得到前n个数的约数个数非常巧妙,首先根据约数个数定理:
对于一个大于1正整数n可以分解质因数:n = p1^d1 + p2^d2 + … + pk^dk,其中pi为素数
则n的正约数的个数就是 :facnum[n] = (1 + d1) * (1 + d2) * … * (1 + dk)
我们需要一个辅助数组d[i],表示i的最小质因子的次幂,(最小的原因是素数筛里每次都是用最小的质因子来筛合数的),还是三种情况:
1.当i为素数时,facnum[i] = 2;d[i] = 1,很好理解
2.当i % p[j] != 0时,gcd(i, p[j]) =1,由积性函数的性质可得facnum[i * p[j]] = facnum[i] * facnum[p[j]] = facnum[i] * 2
d[i * p[j]] = 1(无平方因子)
3.当i % p[j] == 0时,出现平方因子,最小质因子的次幂加1,因此有facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2)
d[i * p[j]] = d[i] + 1
由此得到前n个数的约数个数筛:

void get_facnum()  {      int pnum = 0;      facnum[1] = 1;      for(int i = 2; i < MAX; i++)      {          if(!noprime[i])          {              p[pnum ++] = i;                 facnum[i] = 2;                d[i] = 1;                }          for(int j = 0; j < pnum && i * p[j] < MAX; j++)          {              noprime[i * p[j]] = true;              if(i % p[j] == 0)              {                  facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2);                   d[i * p[j]] = d[i] + 1;                   break;              }              facnum[i * p[j]] = facnum[i] * 2;              d[i * p[j]] = 1;           }      }  }  

四合一

void get_all()  {      int pnum = 0;      phi[1] = 1;      mob[1] = 1;      facnum[1] = 1;      for(int i = 2; i < MAX; i++)      {          if(!noprime[i])          {              phi[i] = i - 1;              mob[i] = -1;              p[pnum ++] = i;                 facnum[i] = 2;                d[i] = 1;                }          for(int j = 0; j < pnum && i * p[j] < MAX; j++)          {              noprime[i * p[j]] = true;              if(i % p[j] == 0)              {                  phi[i * p[j]] = phi[i] * p[j];                  mob[i * p[j]] = 0;                  facnum[i * p[j]] = facnum[i] / (d[i] + 1) * (d[i] + 2);                   d[i * p[j]] = d[i] + 1;                   break;              }              phi[i * p[j]] = phi[i] * (p[j] - 1);              mob[i * p[j]] = -mob[i];              facnum[i * p[j]] = facnum[i] * 2;              d[i * p[j]] = 1;           }      }  }  

最后吐槽一下,对于素数筛里的判断函数,最好用noprime,因为全局默认值为false,有的时候MAX为1e7之类的,memset成true也费不少

阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 酒驾罚款2年没交怎么办 法院判罚款没有钱怎么办 有人朝你吐口水怎么办 孕妇用了六神花露水怎么办 出车祸人不赔钱怎么办 交通事故对方保险不签字怎么办 肇事车主联系不上怎么办 商标被别人申请无效宣告怎么办 淘宝上传宝贝没有品牌怎么办 萌虎白卡借款2000逾期了怎么办 最里面的牙齿烂了怎么办 金龙沉底不游怎么办 龙鱼尾巴烂了怎么办 微博忘了登录名和密码怎么办 新浪微博密码忘了怎么办 vivo手机用户密码忘了怎么办 微博登录名忘记了怎么办 微信忘记密码钱怎么办 微信密码申诉失败怎么办 微博密码被盗了怎么办 对公密码器丢了怎么办 手机qq忘记旧密码怎么办 qq旧密码忘记了怎么办 微博账户密码忘记了怎么办 三星盖乐世s3忘记密码了怎么办 微博登录密码忘记了怎么办 助学贷款密码忘了怎么办 助学贷款的密码忘了怎么办 助学贷款登录密码忘了怎么办 抖音换了手机登不了怎么办 微信忘记密码申诉失败怎么办 银行卡短信通知没了怎么办 斗鱼手机号被绑定了怎么办 网易邮箱不记得账号怎么办 qq号被盗申诉不回来怎么办 号被盗了申诉不回来怎么办 富士康离职后工资卡号没填怎么办? 填高考志愿时没填电话号码怎么办 铁路12306密码忘了怎么办 微销通提示网络请求失败怎么办 苹果手机出现不停播报情况怎么办