算法:离散化 之 区间和

题目

假定有一个无限长的数轴,数轴上每个坐标都是0.
现在,我们首先进行n次操作,每次操作将某一位置x上的数加c。
然后,进行 m 次询问,每个询问包含两个整数l和r,你需要求出在区间[l, r]之间的所有数的和。

输入格式

第一行包含两个整数 n 和 m;
接下来的n行,每行包含两个整数 x 和 c;
再接下来的 m 行,每行包含两个整数 l 和 r。

输出格式

共m行,每行输出一个询问中所求的区间内数字和。

数据范围

−109 ≤ x ≤ 109 ,
1 ≤ n,m ≤ 105,
−109≤ l ≤ r ≤ 109,
−10000 ≤ c ≤ 10000

输入样例

3 3 1 2 3 6 7 5 1 3 4 6 7 8 \begin{matrix} 3 & 3 \\ 1 & 2 \\ 3 & 6 \\ 7 & 5 \\ 1 & 3 \\ 4 & 6 \\ 7 & 8 \end{matrix} 31371473265368

输出样例

8 0 5 \begin{matrix} 8 \\ 0 \\ 5 \end{matrix} 805

思路

因为数轴上的范围很大,而需要用到的坐标数量很少,直接开对应数量级范围的数组显然不可取;离散化即为映射,将使用到的不连续的数字坐标映射到连续的数组的下标,可以减少空间的需求和计算量;

伪代码步骤

1,因为要用到数轴上的坐标,则将所有用到的坐标全部放在数组alls中;
2,对数组中的元素进行排序、去重,得到数组a;
3,n次操作中,每次操作将某一位置x上的数加上c;加到数组sum中;
4,根据sum数组求其前缀和数组s;
5,m次操作,每次操作是求区间和;即通过前缀和数组s进行求解即可;
注意:下标转换,边界条件

代码

import java.util.*;
public class Main{
	static int n;
	static int m;
	static int[][] arr;  //相加数组
	static int[][] interval;  //区间数组
	static int[] sum;
	static int[] s;  //前缀和数组
	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		//alls数组用来存储输入数据的所有使用的坐标
		List<Integer> alls = new ArrayList<>();
		arr = new int[n][2];
		for(int i = 0; i < n; i++){
			for(int j = 0; j < 2; j++){
				arr[i][j] = sc.nextInt();
				alls.add(arr[i][0];
			}
		}
		interval = new int[m][2];
		for(int i = 0; i < m; i++){
			for(int j = 0; j < 2; j++){
				interval[i][j] = sc.nextInt();
				alls.add(interval[i][j];
			}
		}
		//alls数组排序
		Collections.sort(alls); //Collections.sort()用于集合排序
		//通过去重函数unique()进行去重
		int[] a = unique(alls);
		//根据给定的数据 ,将数轴上的某一位置x上的数加c
		sum = new int[a.length];
		for(int i = 0; i < arr.length; i++){
			//调用find()函数,求在排序去重数组映射后的新坐标值
			int index = find(arr[i][0], a);
			sum[index - 1] += arr[i][1];
		}
		s = new int[sum.length + 1];  //前缀和数组
		s[0] = 0;
		//求前缀和数组
		for(int i = 1; i < s.length; i++){
			s[i] = s[i - 1] + sum[i - 1];
		}
		//计算输出区间和
		for(int i = 0; i < interval.length; i++){
			int l = find(interval[i][0], a);
			int r = find(interval[i][1], a);
			//用前缀和数组来计算区间和(第l个到第r个之间的和)
			System.out.println(s[r] - s[l - 1]);
		}
	}
	//去重函数---因为给定的数据中会有重复,需要去重再映射才能得到新坐标
	public static int[] unique(ArrayList<Integer> list){
		List<Integer> tmp= new ArrayList<>();
		tmp.add(list.get(0);
		int i = 0, j = 1;
		while(j < list.size()){
			if(list.get(j) != list.get(i)){
				i = j;
				tmp.add(list.get(i);
			}
			j++;
		}
		//将结果存到数组中
		int[] ans = new int[tmp.size()];
		for(int k = 0; k < tmp.size(); k++){
			ans[k] = tmp.get(k);
		}
		return ans;
	}
	//find()函数用来找指定坐标映射后的新下标
	//因为a数组中是排序去重后的原坐标,所以可以用二分法模板实现
	public static int find(int x, int[] a){
		int l = 0, r = a.length - 1;
		while(l < r){
			int mid = l + r >> 1;
			if(a[mid] >=  x){
				r = mid;
			}
			else{
				l = mid + 1;
			}
		}
		return l + 1; //加1是为了前缀和数组对应的区间和好计算
	}	
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值