CODEVS3550 不一样的根号算法

博客介绍了如何在OI算法中利用O(sqrt(n))复杂度的根号算法解决CODEVS 3550问题。面对序列的单点修改和极值查询,通过结合线段树与暴力枚举,确定公差d的分界线为K=sqrt(M*N/(M+N)*logN),使得算法复杂度达到O(M*sqrt(M*logN))。在实际实现时,K取值为15以优化常数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家都知道在OI算法当中复杂度大都是O(N)、O(N^2)、O(NlogN)......比较常见,而O(sqrt(n))的复杂度并不常见,只有分块、朝鲜树(似乎并不常用但确实复杂度带根号)但是在某些意想不到的地方,可以使用根号算法,不仅效果不错,还比较好写。

引出我们今天的题目CODEVS 3550,已知序列a_1,a_2,...a_i...,a_n,可以进行单点修改,(输入格式为(0,x,y)三元组,意即a_x=a_x+y),可以进行极值查询(输入格式为(1,x,d)三元组,意即查询a_x,a_x+d,a_x+2*d...a_x+k*d中最大值,其中满足x+k*d<=n并且x+(k+1)*d>n(通俗一点来说就是,查询一个首项为x,公差为d的等差数列的数组下标集合所对应的元素极值)

我们所学过的数据结构大多支持连续的区间查询,这样零散的数据怎么办呢。我们可以发现,如果公差d比较小,我们可以对于每种公差d建立d棵线段树,分别保存首项为1,2,......,n-1,n公差为d的下标集合所对应的数;但这种方法在d较大时会导致空间和时间的浪费。如果公差d比较大,我们可以直接暴力枚举。于是很容易想到把两种方法结合起来。那么问题来了,d的分界线在哪呢。

设d的分界限为K,序列长度为N,操作数为K,可以算出时间复杂度为O(N*K*logN(建树)+M*logN(线段树查询)+M*N/K(暴力查询)+M*K*logN(线段树修改)),我们发现这是一个对勾函数,当K=sqrt(M*N/(M+N)*logN),考虑到N与M大致相同,简化为sqrt(M/logN)。这样算法的复杂度变为O(M*sqrt(M*logN))。根号便奇怪的出现在了时间复杂度当中。

在实际实现过程当中,由于线段树常数过大,K应取小一点。

code:(考虑到上面的K太难算了,我取了个常数15= =)

#include<iostream>
#include<cstdio>
#include<cstring>
#define mid (l+r)/2
#define lch t,i<<1
### C++ 中实现开根号算法的方法 在 C++ 编程中,可以采用多种方式来实现开根号的功能。以下是几种常见的方法及其对应的代码示例。 #### 方法一:使用标准库 `sqrt` 函数 C++ 提供了 `<cmath>` 库中的 `sqrt` 函数用于快速计算平方根。这种方法简单高效,适合大多数场景。 ```cpp #include <iostream> #include <cmath> // 包含 sqrt 函数 int main() { double x = 4.0; double result = sqrt(x); std::cout << "4 的平方根是 " << result << std::endl; return 0; } ``` 该方法利用了标准库函数,无需额外编写逻辑即可完成开根号操作[^1]。 --- #### 方法二:基于雷神之锤 III 游戏的快速开方算法 这是一种经典的优化算法,最初由 John Carmack 在《Quake III Arena》游戏中提出。它通过位运算和牛顿迭代法相结合的方式实现了高效的平方根倒数近似计算。 ```cpp float Sqrt(float x) { if (x <= 0) return 0; const float xhalf = x * 0.5f; // 输入值的一半 int i = *(int*)&x; // 转换为整型存储形式 i = 0x1FBD1E2C + (i >> 1); // 近似公式调整 x = *(float*)&i; // 转回浮点型 x = x / 2 + xhalf / x; // 牛顿迭代一次 x = x / 2 + xhalf / x; // 再次迭代提高精度 return x; } ``` 这种算法的核心在于魔术数字 `0x1FBD1E2C` 和移位操作 `(i >> 1)`,它们共同作用于初始猜测值的生成过程[^2]。 --- #### 方法三:牛顿迭代法手动实现 如果希望完全依赖外部库,则可以通过牛顿迭代法自行实现平方根功能。其基本原理是从某个初值出发逐步逼近真实解。 ```cpp std::pair<double, int> square(double x) { double guess = x; // 初值设为输入本身 int iterations = 0; // 记录迭代次数 while (fabs(guess * guess - x) > 1e-6) { // 设置收敛条件 guess = (guess + x / guess) / 2; // 更新公式 ++iterations; } return {guess, iterations}; // 返回结果与迭代次数 } int main() { auto [result, count] = square(2.0); std::cout << "2 的平方根是 " << result << ", 经过 " << count << " 次迭代." << std::endl; return 0; } ``` 此方法仅能够得到最终结果,还能统计所需的迭代步数以便分析性能表现[^3]。 --- #### 方法四:二分查找法求解平方根 对于某些特定需求(如教学目的),也可以考虑使用二分查找策略寻找满足误差范围内的解。 ```cpp double bsqrt(double x) { const double eps = 1e-6; // 定义允许的最大误差 double low = 0.0, high = x; // 初始化上下界 while (high - low > eps) { // 当区间宽度大于阈值时继续循环 double mid = (low + high) / 2.0; if ((mid * mid - x) >= eps) high = mid; // 若当前值过大则缩小上限 else low = mid; // 否则增大下限 } return low; // 输出接近目标值的一个端点 } ``` 相比其他技术而言,这种方式更容易理解但也可能稍显低效[^5]。 --- #### 方法五:自定义输出格式化处理 当需要控制打印的小数位数或者遵循特殊的数据展示规则时,可结合流操纵符定制显示效果。 ```cpp #include <iomanip> // 假定已知两点坐标分别为 A(x1,y1),B(x2,y2),需求数学意义上的欧几里得距离 double distanceBetweenPoints(double x1, double y1, double x2, double y2){ double dx = x1 - x2; double dy = y1 - y2; return sqrt(dx*dx + dy*dy); } int main(){ double px1=1.0 ,py1=2.0 ,px2=4.0 ,py2=6.0 ; double dist = distanceBetweenPoints(px1, py1, px2, py2); std::cout<<std::fixed<<std::setprecision(2)<<dist<<'\n'; return 0; } ``` 这里展示了如何借助 `<iomanip>` 头文件里的工具精确指定数值呈现样式[^4]。 --- ### 总结 以上介绍了五种同的 C++ 平方根实现途径,各有优劣适用于同场合。实际开发过程中应根据具体项目背景选取最合适的方案加以应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值