123
小蓝发现了一个有趣的数列,这个数列的前几项如下:
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)
假设有一个从小到大排列好的数组,比如 [ 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。
数列中的每一个连续的部分可以看作一个小块,第 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
#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;
}