C -求和公式(整除分块)

本文介绍了C语言中利用整除分块优化求和公式的方法,通过实例解析如何快速计算求和问题,包括求和序列的规律分析,以及等差数列求和的应用,降低时间复杂度至线性。同时,文章提供了针对复杂求和问题的变形技巧和解题思路。

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

​ 先讲下整除分块是个啥:要求 ∑ i = 1 n \sum_{i=1}^n i=1nn/i 的值,这时候暴力需要O(n)的时间。由于这个区间是连续的,且’/'是向下取整,当i不能整除k时,n/i会等于最小的i(也就是区间最左边的值 L)除n的商。此时如果可以很快的找到这一个区间,那么就可以将时间复杂度降到O( n \sqrt{n} n )。 接下来讲一下怎么去找这个区间:

​ 如果需要求 ∑ i = 1 20 20 i \sum_{i=1}^{20} \frac{20}{i} i=120i20,把这些要求的样例都写出来找找规律:

i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
20/i 20 10 6 5 4 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1

​ 看到这个表不难发现规律,用20再去除以 (20/i) 就等于最后一个等于这个值的数,比如说 当i=7时,20/i=2,那么用20/(20/7) = 10, 这个时候10就是20/i等于2的最后一个值。可以利用这个特性,在区间最左边用O(1)的时间就可以计算出区间最右边的坐标。在这个区间内,所有的值都是相同的,所以找到这个区间后,直接用区间长度乘以单个数值就ok。

#include<bits/stdc++.h>
using namespace std;

int n, ans;

int main() {
   
		scanf("%d", &n);
  	for(int l = 1, r;l <= n; l = r+1) {
   
      	r = n/(n/l); // 区间最右边
      	ans += (n/l) * (r-l+1);
    }
  	printf("%d\n", ans);
}

下一题:

余数求和 要求 ∑ i = 1 n \sum_{i=1}^n i=1nk%i。

∑ i = 1 n \sum_{i=1}^n i=1nk%i

= ∑ i = 1 n \sum_{i=1}^n i=1nk-i*(k/i)*

=n*k - ∑ i = 1 n \sum_{i=1}^n i=1n i * (k/i)

​ 在每一段(L,R)中 k/i = k/L ,所以在相加的时候可以当作公因式提出来。 ∑ i = 1 n \sum_{i=1}^n i=1ni 相当于一个等差数列。由等差数列求和公式可得: (R-L+1) * (L+R) / 2。

​ 所以每一段(L,R)的和可以表示为 k/L * (R-L+1) * (L+R) / 2。

#include <bits/stdc++.h>
<< 该代码片段的功能是对某个整数 `n` 进行计算,并返回结果值 `res`。以下对该代码的详细说明: ### **功能与逻辑** 这段代码的核心在于利用分块优化(也称为“整除分块”)技巧高效地计算某些涉及大量重复项的数学表达式。 #### 分析每一步: 1. 初始化变量 `res = 0`,用于存储最终的结果。 2. 使用循环结构进行迭代操作: - 循环从左边界 `l = 1` 开始到右边界 `r` 结束; - 每次更新右边界 `r` 的公式为: ```cpp r = n / (n / l); ``` 它的作用是找到当前块中具有相同商 `(n/l)` 的最大连续区间 `[l, r]`。 3. 计算每个区间的贡献并累加到总和 `res` 中: - 当前区块对答案的贡献被表示为下述形式: \[ res += \left(\frac{n}{l}\right) \times (\text{sum from } l \text{ to } r) \] - 其中的求和部分使用等差数列求和公式直接得到: \[ \text{Sum} = \frac{(l + r)}{2} \cdot (r-l+1) \] 4. 更新 `l = r + 1` 继续处理下一个区域直到达到上限条件为止。 --- ### **完整示例程序** 下面是包含此函数的一个完整的 C++ 示例程序,以及对其进一步测试验证的过程。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; // 核心函数定义 ll cal(int n){ ll res = 0; for(ll l = 1, r; l <= n; l = r + 1){ // 找出右侧端点 'r' r = n / (n / l); // 累积结果加上对应段内的所有元素之和乘以其因子(n/i) res += ((ll)(n / l)) * (l + r) * (r - l + 1) / 2 ; } return res; } int main(){ int t,n; cin>>t;// 输入案例数量 while(t--){ cin>>n; cout<<cal(n)<<endl; } } ``` --- ### 解释 上述代码实现了基于"整除分块"算法的快速求解方案: - 对于给定正整数 \( n \),我们需要找出所有的 \( i(1\leqslant i \leqslant n)\) 并且对于每一个这样的\(i\) 加上对应的数值 \((n/i)*i*\) 。然而简单枚举会很慢 O(N), 故我们采用整除分块的方法将其降至O(sqrt N). - 在每次主循环内确定了有效范围 [l,r], 利用这一范围内共同拥有的性质简化复杂度. --- **
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值