题目链接
题目描述
小蓝发现了一个有趣的数列,这个数列的前几项如下:
1
,
1
,
2
,
1
,
2
,
3
,
1
,
2
,
3
,
4
,
.
.
.
1,1,2,1,2,3,1,2,3,4,...
1,1,2,1,2,3,1,2,3,4,...
小蓝发现,这个数列前
1
1
1项是整数
1
1
1,接下来
2
2
2项是整数
1
1
1至
2
2
2,接下来
3
3
3项是整数
1
1
1至
3
3
3,接下来
4
4
4项是整数
1
1
1至
4
4
4,依此类推。
小蓝想知道,这个数列中,连续一段的和是多少。
输入描述
输入的第一行包含一个整数
T
T
T,表示询问的个数。
接下来
T
T
T 行,每行包含一组询问,其中第
i
i
i行包含两个整数
l
i
l_i
li和
r
i
r_i
ri,表示询问数列中第
l
i
l_i
li个数到第
r
i
r_i
ri个数的和。
输出描述
输出 T T T行,每行包含一个整数表示对应询问的答案。
预备知识
二分查找
二分查找是一种在有序数组(数组必须是有序的,一般都是没有重复的数)中查找特定元素的搜索算法。它的工作原理是不断将数组分成两半,然后确定目标元素可能存在的那一半,直到找到目标元素或确定元素不存在。时间复杂度为 O ( l o g n ) O(log n) O(logn)
基本步骤
- 确定搜索区间的左右边界 l e f t left left 和 r i g h t right right。
- 计算中间位置 m i d mid mid,比较中间元素与目标值:
- 若中间元素等于目标值,返回 mid。
- 若中间元素小于目标值,说明目标在右半区,调整左边界 l e f t = m i d + 1 left = mid + 1 left=mid+1。
- 若中间元素大于目标值,说明目标在左半区,调整右边界 r i g h t = m i d − 1 right = mid - 1 right=mid−1。
- 重复上述步骤,直到找到目标值或区间无效。
假设有一个从小到大排列好的数组,比如
[
1
,
3
,
5
,
7
,
9
,
11
,
13
,
15
]
[1, 3, 5, 7, 9, 11, 13, 15]
[1,3,5,7,9,11,13,15],我要找数字9的位置。按照二分查找的步骤,应该是这样的:
中间元素是
7
7
7,比
9
9
9小,所以下一步应该在右半部分继续查找。这时候右半部分是
[
9
,
11
,
13
,
15
]
[9, 11, 13, 15]
[9,11,13,15],再取中间元素
11
11
11,发现比
9
9
9大,所以左半部分是
[
9
]
[9]
[9]。这时候中间元素就是
9
9
9,找到索引
4
4
4。
关键细节
- 区间定义:
- 左闭右闭 [ l e f t , r i g h t ] [left, right] [left,right]:初始 l e f t = 0 , r i g h t = n u m s . s i z e ( ) − 1 left = 0, right = nums.size() - 1 left=0,right=nums.size()−1。
- 左闭右开 [ l e f t , r i g h t ) [left, right) [left,right):初始 r i g h t = n u m s . s i z e ( ) right = nums.size() right=nums.size(),循环条件为 while ( l e f t < r i g h t ) (left < right) (left<right)。
- 中间值计算:
- 推荐写法: m i d = l e f t + ( r i g h t − l e f t ) / 2 mid = left + (right - left) / 2 mid=left+(right−left)/2,避免 ( l e f t + r i g h t ) (left + right) (left+right)溢出。
- 终止条件:
- 左闭右闭:while ( l e f t < = r i g h t ) (left <= right) (left<=right),当 l e f t > r i g h t left > right left>right 时结束。
- 左闭右开:while ( l e f t < r i g h t ) (left < right) (left<right),当 l e f t = = r i g h t left == right left==right 时结束。
解题思路
数列中的每一个连续的部分可以看作一个小块,第 i i i个小块包含 i i i个元素,依次为 1 , 2 , … , i 1,2,…,i 1,2,…,i,每个分块的最后一个元素的位置为前 i i i组的累加项数,即 i ( i + 1 ) / 2 i(i+1)/2 i(i+1)/2。
1 12 123 1234 12345 ..
用
i
d
x
idx
idx数组存储每个分块的结束位置,
i
d
x
[
k
]
=
k
(
k
+
1
)
/
2
idx[k] = k(k+1)/2
idx[k]=k(k+1)/2。
用
s
u
m
sum
sum数组存储前k个分块的元素和之和。每个分块的和为
1
+
2
+
⋯
+
k
=
k
(
k
+
1
)
/
2
1+2+⋯+k=k(k+1)/2
1+2+⋯+k=k(k+1)/2,因此
s
u
m
[
k
]
=
s
u
m
[
k
−
1
]
+
k
(
k
+
1
)
/
2
sum[k] = sum[k-1] + k(k+1)/2
sum[k]=sum[k−1]+k(k+1)/2。
使用二分查找确定
x
x
x所包含的的完整分块
N
N
N(满足
i
d
x
[
k
]
<
=
x
idx[k] <= x
idx[k]<=x)。前N个分组的和为
s
u
m
[
N
]
sum[N]
sum[N],剩余项属于第
N
+
1
N+1
N+1组的前
m
m
m项(
m
=
x
−
i
d
x
[
N
]
m=x−idx[N]
m=x−idx[N]),其和为
m
(
m
+
1
)
/
2
m(m+1)/2
m(m+1)/2。前
x
x
x项的就为
s
u
m
[
N
]
+
m
(
m
+
1
)
/
2
sum[N]+m(m+1)/2
sum[N]+m(m+1)/2。
对于每个查询
[
l
,
r
]
[l,r]
[l,r],结果为前
r
r
r项的和减去前
l
−
1
l−1
l−1项的和。
完整代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll Limit = 1e12 + 7; // 预处理到足够大的分块,覆盖可能的查询范围
vector<ll> idx, sum; // idx存储分块结束位置,sum存储前i个分块的累计和
ll T, l, r;
// 二分查找,找到最大的k使得idx[k] <= x
ll find(ll x) {
ll l = 0, r = idx.size() - 1;
while (l <= r) {
ll mid = l + (r - l) / 2;
if (x == idx[mid]) return mid;
if (x > idx[mid]) l = mid + 1;
else r = mid - 1;
}
return r; // 返回最后一个满足条件的索引
}
// 计算前x项的和
ll qry(ll x) {
ll tidx = find(x); // 找到x所在的分块前一个分块的索引
ll m = x - idx[tidx]; // 当前分块中的前m项
return sum[tidx] + m * (m + 1) / 2; // 前缀和 + 当前分块部分和
}
int main() {
// 预处理idx和sum数块
idx.push_back(0); // 初始分块结束位置为0(第0块)
sum.push_back(0); // 初始和为0
ll i = 1;
while (1) {
ll end_pos = i * (i + 1) / 2; // 第i块的结束位置
if (end_pos > Limit) break; // 超过Limit时停止预处理
idx.push_back(end_pos); // 记录分块结束位置
// 计算前i块的累计和:sum[i] = sum[i-1] + 当前块的和(i*(i+1)/2)
sum.push_back(sum.back() + i * (i + 1) / 2);
i++;
}
cin >> T;
while (T--) {
cin >> l >> r;
// 区间和 = 前r项和 - 前(l-1)项和
cout << qry(r) - qry(l - 1) << endl;
}
return 0;
}
231

被折叠的 条评论
为什么被折叠?



