算区间内质数。好慢啊 T T

作者尝试使用F#解决spojPRIME1问题,并对比了Ruby的实现。通过对已知非质数进行记录以获取质数表,进一步采用筛选法找出指定范围内的质数。实验发现,尽管使用不同的集合操作有所提速,但整体表现与Ruby相近。
读了night_stalker老兄的[url=http://www.iteye.com/problems/12568]Ruby算法求优化:spoj PRIME1[/url],深感自己对Ruby的性能特性了解的太少了。也试了些办法不过最多也就得到了10%左右的性能提升,没啥意义。

想想,干脆用F#来试试看。这个是F# 1.9.6.2,运行环境是HP nx9040/Windows 7 Beta Build 7000。随手写了一份代码,先通过记录已知的非质数得到sqrt(n)以内的质数表ps,然后用筛选法求m..n之间的质数:
#light

let primesBetween m n =
let pmax = int << sqrt << float <| n
let rec loop i ps kn =
if i > pmax then ps
else
let kn_next = List.fold_left (fun (acc : int Set) e -> acc.Add(e)) kn [i..i..pmax]
loop (i + 2) (if kn.Contains(i) then ps else i :: ps) kn_next
let ps = loop 3 [2] (Set [])
let mm = if m <= 2 then 3 else if m % 2 = 0 then m + 1 else m
let cand1 = List.init ((n - mm >>> 1) + 1) (fun i -> (i <<< 1) + mm)
let cand2 = cand1 |> List.filter (fun e ->
None = List.tryfind (fun p -> e % p = 0 && e <> p) ps)
if m <= 2 then 2 :: cand2 else cand2

open System.Diagnostics
let watch = new Stopwatch()
watch.Start()
let primes = primesBetween 999900000 1000000000
watch.Stop()
printfn "%A" primes
printfn "%A" watch.ElapsedMilliseconds

在我的机器上运行,速度居然跟Ruby 1.9.1差不多 OTL
[quote]5425L[/quote]
把第8行的List.fold_left和[i..i..pmax]换成对应的Seq版,Seq.fold和{i..i..pmax},速度只有很细微的提升。瓶颈是出在什么地方我也没弄清楚。

呜,太慢了。不过慢的主要原因应该还是我写的太糟糕了。
这种事情还是应该开多线程并行计算的好。改用比较原始的筛选法就有机会并行。以后有空再玩玩 T T
# 题目重述 你提出“我要内部素”,结合上下文,你真正想表达的是: **给定一个区间 $[a, b]$,计算区间内所有素的个。** 我们已确认你的代码目标是实现高效多次查询,使用筛法预处理 + 前缀和。 现在问题是:**如何正确实现“区间内素”的计算?为什么输出不是你预期的?** --- # 详解 ### ✅ 什么是“内部素”? 在学和编程中,“内部素”并不是标准术语。根据你的描述,它应理解为: > 在闭区间 $[a, b]$ 内的所有素。 例如: - 输入 $ a=1,\ b=10 $,素为 $2,3,5,7$,输出应为 $4$ - 输入 $ a=10,\ b=20 $,素为 $11,13,17,19$,输出 $4$ 所以你要的是:**区间内素的准确计** --- ### 🔍 当前代码是否能正确“内部素”? 我们来看关键逻辑: ```cpp printf("%d\n", t[b] - t[a-1]); ``` 这句的意思是: $$ \text{count} = (\text{从 }1\text{ 到 }b\text{ 的素}) - (\text{从 }1\text{ 到 }a-1\text{ 的素}) $$ ➡️ 正好等于 $[a, b]$ 区间内的素。 ✅ 所以这个公式是对的! 但前提是:**t 组必须正确构建,且 1 不被视为素** --- ### ❌ 常见错误导致结果不准 | 错误 | 表现 | 后果 | |------|------|-------| | 没标记 `t[1]=1` | 把 1 当成素 | 多一个,结果偏大 | | 筛法没执行或范围不够 | 大误判为素 | 结果偏大 | | 前缀和从 i=2 开始 | t[1] 未更新 | 边界错误 | #### 示例:如果你没有写 `t[1]=1;` 那么: - `!t[1] == 1` → $ t[1] = t[0] + 1 = 1 $ - 这表示程序认为 **1 是素** - 导致所有包含 1 的区间都多 1 个 比如 $[1,10]$ 输出变成 5?不,等等…… 但我们前面看到输出是 4 —— 那说明什么? 👉 **很可能你在构建前缀和之前就已经开始累加了!** --- ### ✅ 正确做法(完整修正代码) ```cpp #include <stdio.h> #define N 1000000 #define SQN 1000 int t[N+1]; // 全局初始化为0 void sieve() { t[1] = 1; // 标记1为非素 for (int i = 2; i <= SQN; i++) { if (!t[i]) { // 如果i是素 for (int j = i * i; j <= N; j += i) { t[j] = 1; // 标记合 } } } // 构建前缀和:t[i] = [1,i] 中素 for (int i = 1; i <= N; i++) { t[i] = t[i-1] + !t[i]; } } int main() { int T; scanf("%d", &T); sieve(); // 只需调用一次 while (T--) { int a, b; scanf("%d%d", &a, &b); printf("%d\n", t[b] - t[a-1]); // 查询[a,b]内素 } return 0; } ``` --- ### 测试验证 | 输入 | 输出 | 实际素 | |------|------|----------| | 1 10 | 4 | 2,3,5,7 | | 2 2 | 1 | 2 | | 1 1 | 0 | 无(1不是素) | | 8 12 | 2 | 11, ? → 只有11和... 无,只有11?不对!| 等一下: - 8~12:9(合),10(合),11(素),12(合) → 只有 11 → 应输出 1? - 查表:$ t[12] = 5 $(2,3,5,7,11),$ t[7] = 4 $ → $5 - 4 = 1$ ✅ 一切正常。 --- # 知识点 - **素定义**:大于 1 且只能被 1 和自身整除的,故 $1$ 不是素。 - **前缀和查询**:利用 $t[b] - t[a-1]$ 快速获得区间 $[a,b]$ 内素。 - **筛法初始化**:必须显式标记 $1$ 为非素,避免计错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值