• 快速求素数和


    在知乎上看到一个问题:求十亿内所有质数的和,怎么做最快?

    记录一下第一回答

    定义S(v,p)2v所有整数中,在普通筛法中外层循环筛完p时仍然幸存的数的和。因此这些数要不本身是素数,要不其最小的素因子也大于p。因此我们需要求的是S(n,lfloorsqrt n
floor),其中n是十亿。

    为了计算S(v,p),先考虑几个特殊情况

    1. ple1。此时所有数都还没有被筛掉,所以S(v,p)=sum_{i=2}^{v}i=frac{(2+v)(v-1)}{2}
    2. p不是素数。因为筛法中p早已被别的数筛掉,所以在这步什么都不会做,所以此时S(v,p)=S(v,p-1)
    3. p是素数,但是v<p^2。因为每个合数都一定有一个不超过其平方根的素因子,如果筛到p时还没筛掉一个数,那么筛到p-1时这个数也还在。所以此时也有S(v,p)=S(v,p-1)


    现在考虑最后一种稍微麻烦些的情况:p是素数,且p^2le v
    此时,我们要用素数p去筛掉剩下的那些数中p的倍数。注意到现在还剩下的合数都没有小于p的素因子。因此有:
    S(v,p)=S(v,p-1)-sum_{substack{2le k le v,\ pmbox{为}kmbox{的最小素因子}}}k

    后面那项中提取公共因子p,有:
    S(v,p)=S(v,p-1)-p	imessum_{substack{2le k le v,\ pmbox{为}kmbox{的最小素因子}}}frac{k}{p}

    因为p整除k,稍微变形一下,令t=frac{k}{p},有:
    S(v,p)=S(v,p-1)-p	imessum_{substack{2le t le lfloorfrac{v}{p}
floor,\ tmbox{的最小素因子}ge p}}t

    注意到一开始提到的S的定义(“这些数要不本身是素数,要不其最小的素因子也大于(注意!)p”),此时p后面这项可以用S来表达:
    S(v,p)=S(v,p-1)-p	imes(S(leftlfloorfrac{v}{p}
ight
floor,p-1)-{p-1mbox{以内的所有素数和}})

    再用S替换素数和得到最终表达式:
    S(v,p)=S(v,p-1)-p	imes(S(leftlfloorfrac{v}{p}
ight
floor,p-1)-S(p-1,p-1))


    我们最终的结果是S(n,lfloorsqrt n
floor)。计算时可以使用记忆化,也可以直接自底向上动态规划。
    至于算法的复杂度就留作练习,是低于以上任何一种暴力方法的。

     1 import time
     2 def P10(n):
     3     r = int(n ** 0.5)
     4     # assert r*r <= n and (r+1)**2 > n
     5     V = [n // i for i in range(1, r + 1)]
     6     #    print V
     7     V += list(range(V[-1] - 1, 0, -1))
     8     #print V
     9     S = {i: i * (i + 1) // 2 - 1 for i in V}
    10     #print S
    11     st = time.clock();
    12     for p in range(2, r + 1):
    13         if S[p] > S[p - 1]:  # p is prime
    14             sp = S[p - 1]  # sum of primes smaller than p
    15             p2 = p * p
    16             for v in V:
    17                 if v < p2: break
    18                 S[v] -= p * (S[v // p] - sp)
    19     end = time.clock();
    20     print end - st
    21     return S[n]
    22 
    23 while(True):
    24     N = input()
    25 
    26     print P10(N)

    1e9的数据能在1s能跑出来, 真是神一样的算法, 神一样的代码。

    ————————————————————————更新——————————————————————————————

    之后用C++实现了一遍,发现速度还没 线性筛 快,比较循环部分, 发现python 的速度要比 C++快50+倍,如果去除 list 的影响,dict 与 map的效率可能相差两个数量级,去查找dict 与 map 的实现原理, 原来, dict 是用 hash 复杂度O(1)  而 map为了元素的有序性, 采用红黑树 复杂度为O(logn)。

     1 const int maxn = 1e6;
     2 map<ll, ll> mp;
     3 vector<ll> vr;
     4 ll arr[maxn];
     5 ll solve(ll n){
     6     ll r = sqrt(n);
     7     vr.clear();
     8     mp.clear();
     9     for (int i = 1; i <= r; ++i){
    10         vr.pk(n / i);
    11     }
    12     for (int i = vr.back() - 1; i > 0; --i){
    13         vr.pk(i);
    14     }
    15     int len = vr.size();
    16     for (int i = 0; i < len; ++i){
    17         arr[i] = vr[i];
    18         mp[vr[i]] = vr[i] * (vr[i] + 1) / 2 - 1;
    19     }
    20     ll sp;
    21     int p2;
    22     double st = clock();
    23     for (int i = 2; i <= r; ++i){
    24         if (mp[i] > mp[i - 1]){
    25             sp = mp[i - 1];
    26             p2 = i * i;
    27             for (int v = 0; v < len; ++v){
    28                 if (arr[v] < p2) break;
    29                 mp[arr[v]] -= i * (mp[arr[v] / i] - sp);
    30             }
    31         }
    32     }
    33     double ed = clock();
    34     cout << ed - st << endl;
    35     return mp[n];
    36 }
    37 
    38 int main(){
    39     //freopen("data.out", "w", stdout);
    40     //freopen("data.in", "r", stdin);
    41     //cin.sync_with_stdio(false);
    42     int n;
    43     while (cin >> n){
    44         cout << solve(n) << endl;
    45     }
    46     return 0;
    47 }
  • 相关阅读:
    (转)linux下控制帐户过期的多种方法
    跟老男孩学Linx运维---web集群实战笔记
    (转)企业生产环境用户权限集中管理方案案例
    Linux 运维培训笔记
    (转)sudo配置文件/etc/sudoers详解及实战用法
    (转) RHEL7 忘记密码修改root密码
    (转)Mysql数据库之Binlog日志使用总结CentOS 7.x设置自定义开机启动,添加自定义系统服务
    git 删除远程分支
    crontab详解
    PHP数据库长连接mysql_pconnect用法
  • 原文地址:https://www.cnblogs.com/yeahpeng/p/4440481.html
Copyright © 2020-2023  润新知