第十二届蓝桥杯 #G 123

第十二届蓝桥杯 2021年国赛真题 (Java 大学C组)

问题描述

  小蓝发现了一个有趣的数列,这个数列的前几项如下:
  1 , 1 , 2 , 1 , 2 , 3 , 1 , 2 , 3 , 4 , . . . 
  小蓝发现,这个数列前 1  项是整数 1 ,接下来 2  项是整数 1  至 2 ,接下来 3 项是整数 1 11 至 3 ,接下来 4  项是整数 1  至 4 ,依次类推。
  小蓝想知道,这个数列中,连续一段的和是多少。

输入格式:
        接下来的第一行输入一个整数 T ,表示询问个数。

接下来 T 行,每行包含一组询问,每行两个变量 a 与 b ,表示 查询数列中第 a 个数 到 第 b 个数的和

示例:

输入:
    3
    1 1
    1 3
    5 8

输出:
    1
    4
    8

范围:

        对于10%的评测用例        1<T< 30,1≤a≤b≤100
        对于20%的评测用例        1≤T≤100,1≤a≤b≤1000
        对于40%的评测用例        1≤T≤1000,1≤a≤b≤10**6
        对于70%的评测用例        1≤T≤10000,1≤a≤b≤10**9
        对于80%的评测用例        1≤T≤1000,1≤a≤b≤10**12
        对于90%的评测用例        1≤T≤10000,1≤a≤b≤10**12
        对于所有评测用例           1≤T≤100000,1≤a≤b≤10**12

首先考虑用前缀和,但是区间太大,数组也太大,所以可以变形为

1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
...

同时我们发现每一行的和,刚好等于它在原数列中最后的一个位置

               每行和
1                1                //原数列第 1 个数是这一行最后一个
1 2              3                //原数列第 3 个数是这一行最后一个
1 2 3            6                //原数列第 6 个数是这一行最后一个
1 2 3 4          10               //原数列第 10 个数是这一行最后一个
1 2 3 4 5        15               //原数列第 15 个数是这一行最后一个
...

对于 原数列 第 p 个数,二分查找 找到 每行和 中第 i 个数比它小,第 i+1 个数比它大。则第 p 个数必然在第 i+1 行。

同时,第 p 个数在第 i+1 行中的第 p-i 个。

例子:

               每行和     行
1                1        1 
1 2              3        2    
1 2 3            6        3        
1 2 3 4          10       4        
1 2 3 4 5        15       5        
...

如果 p = 5 
则 p 在 第 3 行 每行和为 6 的行
同时 p(5) 在该行的第 p-i(5-3=2) 的位置

这样只需要确定该数 在 该行的位置 + 之前所有的总和,即可求得从 开始 到该数的全部和

用大的数的全部和 - 小的数的全部和 = 所求值 (注意,小的数不包含它本身,大的数包含)

整体思路如此,下面上代码

public class App {
    public static void main(String[] args) throws Exception {    
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        long arr[][] = new long[n][2];
        long max = 0;
        //获取输入,存入 arr[][] 中
        for(int i=0; i<n; i++){
            arr[i][0] = scan.nextLong();
            arr[i][1] = scan.nextLong();
            max = Math.max(arr[i][1], max);
        }
        long dp[] = new long[100000000];//每一行的和
        int len = 1;
        for(int i=1;i<dp.length;i++){
            dp[i] = dp[i-1] + i;
            len++;
            if(dp[i]>max){
                break;
            }
        }
        long qian[] = new long[len];//前缀和
        for(int i=1;i<len;i++){
            qian[i] = qian[i-1] + dp[i];
        }
        for(int i=0;i<arr.length;i++){
            Long kais = 0l;
            for(int j=0; j<2;j++){
                int z = fen(dp, arr[i][j], len);//dp中位置
                long f = arr[i][j] - dp[z-1];   //当前一行,从1到第f个数
                long sum = 0;
                if(j==0){   //起点
                    sum = (1+f-1)*(f-1)/2;      //等差数列求和
                    kais += sum + qian[z-1];    //当前一行的和 + 之前的全部的和
                }else{      //终点
                    sum = (1+f)*f/2;            //等差数列求和
                    kais = qian[z-1] + sum - kais;  //当前一行的和 + 之前的全部的和 - 第一个数之前的值
                }
            }
            System.out.println(kais);
        }
    }
    //二分查找 返回第 p 个数所在行数的下标
    public static int fen(long[]arr,long value,int len){
        int start = 0;
        int end = len;
        while(true){
            int scend = (start + end) /2;
            if(arr[scend]==value){
                return scend;
            }
            else if(arr[scend]>value){
                end = scend;
            }else{
                start = scend;
            }
            if(end - start == 1){
                return end;
            }
        }
    }
}

整体时间复杂度能够快速求出值,

但是空间复杂度有些大,不过想不出更优解决方案了。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值