123 题解(分块+二分查找)

题目链接

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. 确定搜索区间的左右边界 l e f t left left r i g h t right right
  2. 计算中间位置 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=mid1
  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

关键细节
  1. 区间定义:
  • 左闭右闭 [ 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)
  1. 中间值计算:
  • 推荐写法: m i d = l e f t + ( r i g h t − l e f t ) / 2 mid = left + (right - left) / 2 mid=left+(rightleft)/2,避免 ( l e f t + r i g h t ) (left + right) (left+right)溢出。
  1. 终止条件:
  • 左闭右闭: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[k1]+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=xidx[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 l1项的和。

完整代码

#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;
}

你可能感兴趣的:(算法,数据结构,c++)