本题为2021年蓝桥杯国赛题。
题目描述
小蓝发现了一个有趣的数列,这个数列的前几项如下:
1, 1, 2, 1, 2, 3, 1, 2, 3, 4,⋯
小蓝发现,这个数列前 1 项是整数 1,接下来 2 项是整数 1 至 2,接下来 3 项是整数 1 至 3,接下来 4 项是整数 1 至 4,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少?
输入描述
输入的第一行包含一个整数 T,表示询问的个数。
接下来 T 行,每行包含一组询问,其中第 i 行包含两个整数 l 和 r,表示询问数列中第 l 个数到第 r 个数的和。
输出描述
输出 T 行,每行包含一个整数表示对应询问的答案。
题解:
将数列看成三角矩阵,即第1行只有数:1,第2行只有数:1,2,第3行只有数:1,2,3,如下:
1
1 2
1 2 3
1 2 3 4
……
1 2 3 4 5 …
显然每一行的元素个数是符合等差数列的,即第i行的元素个数是i(说明该行是完整的)。
第一步:获取位置
定义一个函数专门获取该数列的第n项所在的行和列(假设是第x行,第y列),那么对于前x-1行,每一行的数都是完整的,只有在第x行,只有y个数(可能不完整也可能完整)。显然,如果y=x则说明第x行也是完整的,但如果y<x则说明第x行是不完整的排在该行后面有部分数是没有的。因此对于每一行的元素个数是满足等差数列的,可以利用等差数列的求和公式反推其是到第几行是完整的(一元二次方程的求根公式向下取整),假设利用方程解出是t,则可以推出原数列的第n项所在的位置(行,列):x=t+1, y=n-t(t+1)2 。
第二步:求和
题目要求对于每一次查询,都需要输入 l 和 r ,需要输出原数列的第 l 个数到第 r 个数的和。因此我们可以先分别计算出第 l 个数,和第 r 个数所在的位置(行,列),再对其中间的各数求和即可。对于求和,需要考虑一种特殊情况,即数列中的第 l 个数和第 r 个数,所在的位置在同一行,那么对于其之间的和,直接利用等差数列求和公式即可。另一种一般情况,如果不在同一行的话,考虑将其分成三部分求和。
第①部分求和:数列中第 l 个数所在的行:计算该数(该数其实就是数列中第 l 个数所在的列)到该行最后一个数的和
第②部分求和:数列中第 l 个数所在的行到第 r 个数所在的行(不包含 l 和 r ):每一行都是完整的,(整体结构类似一个梯形矩阵),所以也采用等差数列求和公式即可。
第③部分求和:数列中第 r 个数所在的行:计算从1开始到该数(该数其实就是数列中第 r 个数所在的列)的和。
最后将这三部分的和相加即可。
C++参考程序代码如下:
#include <iostream>
#include <cmath>
using namespace std;
struct pos { //表示第x行,第y列的数的位置
long long x,y;
};
pos get(long long n) { //获取数列的第n项所在的位置(行,列)
long long t=(sqrt(1+8*n)-1)/2;
pos p;
if(t*(t+1)/2==n) {
p.x=t;
p.y=t;
} else {
p.x=t+1;
p.y=n-t*(t+1)/2;
}
return p;
}
long long presum(long long n) { //求数列的前n行的各列所有元素的和
return (n*(n+1)*(2*n+1)/6+n*(n+1)/2)/2;
}
int main() {
int T;
cin>>T;
while(T--) {
long long l,r,ans;
cin>>l>>r;
long long x1=get(l).x, y1=get(l).y; //获取数列中第l个数的位置(行,列)
long long x2=get(r).x, y2=get(r).y; //获取数列中第r个数的位置(行,列)
if(x1==x2) { //如果数列中的第l个数和第r个数在同一行
ans=(y1+y2)*(y2-y1+1)/2;
} else { //不在同一行,将其中间分成三部分求和
long long sum1=(y1+x1)*(x1-y1+1)/2; //在第l个数所在的行(第x1行),计算从y1到该行最后一个数(x1)的和
long long r=x2-x1-1,sum2;
sum2=r*(1+x1)*x1/2+r*(1+r)/2*x1+presum(r); //计算x1+1行到x2-1行之间,所有元素的和
long long sum3=(1+y2)*y2/2; //在第r个数所在的行(第x2行),计算1开始到y2的和
ans=sum1+sum2+sum3;
}
cout<<ans<<endl;
}
return 0;
}