杂谈 1:论 \([l, r]\) 之间的非平方数

Part 0 例题

先看一道例题;

给定两个数 \(l, r\),求 \(l \sim r\) 之间有多少个数不是平方数。平方数的定义:是一个数的平方。如 \(16\) 是平方数,因为 \(4^2 = 16\),\(11\) 则不是,因为 \(\sqrt{11}\) 不为整数。数据范围:\(1 \le l, r \le 10^9\)。

有些人一看到题目就像:“这不纯暴力吗?”。是的,大多数人看到题目的第一直觉是暴力。但是随着 \(l, r\) 的增大,暴力算法会跑得很慢,下面介绍几种算法来解决这个问题。

Part 1 纯暴力

直接枚举 \([l, r]\) 区间,对于每个 \(i\) 判断 \(\sqrt{i} - \lfloor \sqrt{i} \rfloor\) 是否为 \(0\)。(因为平方数开根是整数)如果是,计数器 \(+1\)。最后输出 \(r - l + 1\) 减去计数器里的数即可。

代码;

ll ans = 0;
for (int i = l; i <= r; ++ i) {
if (sqrt(i) - int(sqrt(i)) == 0) {
++ ans;
}
}
cout << (r - l + 1) - ans << '\n';

但是,这种算法的时间复杂度是 \(O(r - l + 1)\)。如果 \(r - l + 1\) 超过 \(10^8\) 会超时(注意,sqrt 函数也占用一定时间)。下面就介绍另一种算法。

Part 2 小暴力

这种算法属于暴力,但不是纯暴力。大概的思路就是:定义两个变量 \(A, B\)。从 \(l\) 开始,一个一个数枚举,找到 \([l, r]\) 之间的最小的平方数并存进 \(A\) 里。再从 \(r\) 开始,一个一个数枚举,找到 \([l, r]\) 之间的最大的平方数并存进 \(B\) 里。

如果两个都没找到(即 \(A = B = 0\)),答案为 \(r - l + 1\)(因为 \(A = B = 0\) 表示 \([l, r]\) 之间没有平方数),否则答案为 \((r - l + 1) - (B - A + 1)\)(序列长度减去区间平方数,读者可自证为什么 \(B - A + 1\) 是 \([l, r]\) 之间的平方数个数)。

代码:

for (ll i = l; ; ++ i) {
if (sqrt(i) - int(sqrt(i)) == 0) {
A = sqrt(i);
break;
}
}
for (ll i = r; ; -- i) {
if (sqrt(i) - int(sqrt(i)) == 0) {
B = sqrt(i);
break;
}
}
if (!A && !B) {
cout << r - l + 1 << '\n';
} else {
cout << r - l + 1 - (B - A + 1) << '\n';
}

设离 \(l\) 最近的比 \(l\) 大的平方数是 \(A\), 离 \(r\) 最近的比 \(r\) 小的平方数是 \(B\),这种算法的时间复杂度是 \(O(A - l + r - B)\)。如果 \(A\) 离 \(l\) 太远或 \(B\) 离 \(r\) 太远就会 TLE。

Part 3 二分

我们可以发现,平方数满足单调性(\(1, 4, 9, 16, 25, 36, \cdots\)),所以可以二分。

\(\sqrt{10^9} ≈ 31622\),我们可以构造一个 \(0 \sim 31622\) 的平方数数组(\(a_i = i^2\))。因此,我们可以二分在数组里找答案。找一个 \(L\) 使得 \(L^2 \ge l\),再找一个 \(R\) 使得 \(R^2 > r\),答案就是 \(r - l + 1 - \max(0,\ R - L)\)(跟上个算法的思想差不多,读者可自证)。

代码:

const int kMaxN = sqrt(1e9);
for (int i = 1; i <= kMaxN; ++ i) {
a[i] = i * i;
}
int L = 0, R = sqrt(1e9);
L = lower_bound(a, a + kMaxN, sqrt(l)) - a;
R = upper_bound(a, a + kMaxN, sqrt(r)) - a;
cout << r - l + 1 - max(0, R - L) << '\n';

这种算法的时间复杂度是 \(O(\log 10^9)\),还不够优秀。

Part 4 数学

令 \(A = \lfloor\sqrt{l}\rfloor \le \sqrt{l}\),\(B = \lfloor\sqrt{r}\rfloor \le \sqrt{r}\)。那么 \([l, r]\) 之间的平方数为 \((A + 1)^2, (A + 2)^2, \cdots, (B - 1)^2, B^2\)。如果 \(A^2 = l\),则答案为 \((r - l + 1) - (B - A + 1)\),否则答案为 \((r - l + 1) - (B - A)\)。(跟 Part \(2, 3\) 的思想差不多,读者可自证)

代码:

int A = sqrt(l), B = sqrt(r);
cout << (A * A == l? r - l + 1 - B - A + 1 : r - l + 1 - B - A) << '\n';

时间复杂度 \(O(1)\),极为优秀。

杂谈 1:论 [l, r] 之间的非平方数的更多相关文章

  1. UPC 2224 Boring Counting (离线线段树,统计区间[l,r]之间大小在[A,B]中的数的个数)

    题目链接:http://acm.upc.edu.cn/problem.php?id=2224 题意:给出n个数pi,和m个查询,每个查询给出l,r,a,b,让你求在区间l~r之间的pi的个数(A< ...

  2. HDU 6356 (线段树-l,r 之间小于val 的变val+单点求值)

    题目描述: 给你一个长度为n的最开始为0的数以及m个更新操作以及数据生成器参数X,Y,Z.每次操作,将由数据生成器生成出li,ri,vi.让你从区间[li,ri]中,将所有小于vi的数变为vi.最后让 ...

  3. 查询数组里有多少个数在[L,R]范围中(二分)

    使用两次二分即可得到这个值 比如现在有一个vector<int> vec,里面存放的是有序数列. 我们现在希望找出范围在[L,R]之间的数有多少个. 则有cnt = upper_bound ...

  4. hdu 4630 查询[L,R]区间内任意两个数的最大公约数

    No Pain No Game Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  5. 主席树——求区间[l,r]不同数字个数的模板(向左密集 D-query)

    主席树的另一种用途,,(还有一种是求区间第k大,区间<=k的个数) 事实上:每个版本的主席树维护了每个值最后出现的位置 这种主席树不是以权值线段树为基础,而是以普通的线段树为下标的 /* 无修改 ...

  6. 在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作。一次移动操作指用一个"LX"替换一个"XL",或者用一个"XR"替换一个"RX"。现给定起始字符串start和结束字符串end,请编写代码,当且仅当存在一系列移动操作使得start可以转换成end时, 返回True。

    在一个由 'L' , 'R' 和 'X' 三个字符组成的字符串(例如"RXXLRXRXL")中进行移动操作.一次移动操作指用一个"LX"替换一个"XL ...

  7. luogu P3834 【模板】可持久化线段树 1(主席树) 查询区间 [l, r] 内的第 k 小/大值

    ————————————————版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https:// ...

  8. 中间值为什么为l+(r-l)/2,而不是(l+r)/2

    二分法的算法中,我们看到一些代码里取中间值: MID=l+(r-l)/2; 为什么是这个呢?不就是(l+r)/2吗?为什么要多此一举呢? 其实还是有不一样的,看看他们的区别吧: l,r是指针的时候只能 ...

  9. Uva 3767 Dynamic len(set(a[L:R])) 树套树

    Dynamic len(set(a[L:R])) Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 https://uva.onlinejudge.org/in ...

  10. 【题解】Luogu UVA12345 Dynamic len(set(a[L:R]))

    原题传送门 这题要用动态莫队,我博客里有介绍 这道题和luogu P1903 [国家集训队]数颜色 / 维护队列差不多,解法就在上面那篇博客里qaq 主要的问题是如何排序? 排序有三个关键字: 1.左 ...

随机推荐

  1. 你了解 Java 的逃逸分析吗?

    Java 的逃逸分析 1. 定义 逃逸分析(Escape Analysis)是 JVM 的一种优化技术,用于分析对象的作用域,从而决定对象的分配方式或优化手段. 主要目的是判断一个对象是否会逃离当前方 ...

  2. Typora——锚点

    意思就是只能跳到标题(单击时可能需要按Ctrl),即# 开头的字段,格式很简单,例如对于一个二级标题,单击(按住Ctrl)"我想跳转",可以跳到"跳 到 这 里" ...

  3. 17.6K star!后端接口零代码的神器来了,腾讯开源的ORM库太强了!

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 " 实时零代码.全功能.强安全 ORM 库 后端接口和文档零代码,前端定制返回 J ...

  4. 【记录】IDA|IDA设置text view为默认,并解决IDA7.6打开新固件卡顿的问题

    版本:IDA Pro 7.6 Graph View(控制流视图)其实我真的看得很少,因为遇到分析难题时总是是因为间接调用,它根本分析不出来.但是一开IDA它就自动分析这个特别卡.所以今天想彻底解决一下 ...

  5. 词库过大导致的Redis超时问题-RedisCommandTimeoutException

    问题 Redis缓存超时问题 报错内容 redis io.lettuce.core.RedisCommandTimeoutException: Command timed out after 10 s ...

  6. 图解Spring源码2-Spring Bean元数据体系与Spring容器

    >>>点击去看B站配套视频<<< 系列文章目录和关于我 1. 从一个例子开始 小陈申请加盟咖啡店后,小陈收到总部寄来的<开店规格单>.这份文件允许每家分 ...

  7. Vue中的APP与js的对象字面量

    JavaScript的对象字面量是一种方便创建和初始化对象的语法.它允许您直接在代码中定义对象,而无需使用类或构造函数.对象字面量使用大括号{}括起来,并包含零个或多个键值对. 以下是JavaScri ...

  8. codeup之查找

    Description 输入数组长度 n 输入数组 a[1-n] 输入查找个数m 输入查找数字b[1-m] 输出 YES or NO 查找有则YES 否则NO . Input 输入有多组数据. 每组输 ...

  9. Windows平台调试器原理与编写01.调试框架

    调试器原理与编写01.调试框架-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net 调试框架 调试器最基本功能: 断点,单步 断点分为三类 软件断点 硬件断点 内存断点 ...

  10. AD 横向移动-令牌模拟攻击

    本文通过 Google 翻译 Lateral Movement – Token Impersonation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充. 导航 0 ...