Frequent Values(频繁出现的数值)-RMQ问题-线段树

HDU-1806

Problem Description

You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers ai , ... , aj .
 

Input

The input consists of several test cases. Each test case starts with a line containing two integers n and q (1 ≤ n, q ≤ 100000). The next line contains n integers a1 , ... , an(-100000 ≤ ai ≤ 100000, for each i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following q lines contain one query each, consisting of two integers i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the query.
The last test case is followed by a line containing a single 0.
 

 

Output

For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.

 

 

Sample Input

10 3 -1 -1 1 1 1 1 3 10 10 10 2 3 1 10 5 10 0

 

Sample Output

1 4 3

分析:
首先我们注意到数组是非降序的,所以其肯定是有序的,且所有相等的元素会聚集在一起
我们就可以对这个数组进行一次编码
例如:
1 2 2 3 3 4 5 5 6
可以编码为
(1,1),(2,2),(3,2),(4,1),(5,2),(6,1)
(x,y)表示有x个连续的b
也就是将这个数组进行了分段,接下来利用两个数组
value[i]表示第i段的数字是多少
count[i]表示第i段的数字有多少个
再来一个数组将原数组中每个数字的所在的段进行记录
num[i]表示第i个数字所在段。
left[i]表示第i个数字所在的段左端点
right[i]表示第i个数字所在段的右端点

然后对于每一次询问,(L,R)
1.如果l,r是在一个段中,直接输出r-l+1
2.如果l,r不在一个段中,中间只间隔一个段
    max(right[l]-l,r-left[r],count(nums[l]+1))
3.如果l,r不在一个段中,且中间间隔一大于段,由以下公式得到答案
    max(right[l]-l,r-left[r],rmq[nums[l]+1,nums[r]-1])
其中的rmq就是范围最大值问题,就可以利用线段树进行解决了,表示区间内的出现次数最多的值出现的次数
 

代码实现:实现比较粗糙,只是展示思路。

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		while(true){
			int n = input.nextInt();
			if(n == 0){
				return;
			}
			int q = input.nextInt();
			int[] nums = new int[n];
			for(int i = 0;i<n;i++){
				nums[i] = input.nextInt();
			}
			int[][] query = new int[q][2];
			for(int i=0;i<q;i++){
				query[i][0] = input.nextInt();
				query[i][1] = input.nextInt();
			}
			List<List<Integer>> segment = segment(nums);
			// 由于不需要修改,还是不用线段树那么麻烦的方式了,使用ST吧。空间也就懒得算了。
			int[][] d = new int[segment.get(0).size()][segment.get(0).size()];
		    // 注意,这里是用每个段的数量来构造d,而不是原数组。
			init(d, segment.get(1));
			List<Integer> num = segment.get(4);
			for(int i=0;i<q;i++){
				int l = query[i][0]-1;
				int r = query[i][1]-1;
				if(num.get(l) == num.get(r)){
					System.out.println(r-l+1);
				}else if(num.get(l)+1 == num.get(r)){
					System.out.println(Math.max(r-segment.get(2).get(r)+1, segment.get(3).get(l)-l+1));
				}else if(num.get(r) - num.get(l)>1){
					int max = Math.max(r-segment.get(2).get(r)+1, segment.get(3).get(l)-l+1);
					max = Math.max(query(segment.get(4).get(l), segment.get(4).get(r)-2, d), max);
					System.out.println(max);
				}
			}
		}
	}
	
	public static void init(int[][] d,List<Integer> list){
		for(int i=0;i<list.size();i++){
			d[i][0] = list.get(i);
		}
		for(int i=1;(1<<i)<=list.size();i++){
			for(int j=0;j+(1<<(i-1))<list.size();j++){
				d[j][i] = Math.max(d[j][i-1],d[j+(1<<(i-1))][i-1]);
			}
		}
		
	}
	
	public static int query(int l,int r,int[][] d){
		int k = 0;
		while(1<<(k+1)<=r-l+1){
			k++;
		}
		return Math.max(d[l][k], d[r-(1<<k)+1][k]);
	}
	
	
	
	/**
	 * 获取出上面所列举的所有信息
	 * @param nums
	 * @return
	 */
	public static List<List<Integer>> segment(int[] nums){
		if(nums == null || nums.length == 0){
			return null;
		}
		List<List<Integer>> res = new ArrayList<>();
		List<Integer> value = new ArrayList<>();
		List<Integer> count = new ArrayList<>();
		List<Integer> left = new ArrayList<>();
		List<Integer> right = new ArrayList<>();
		List<Integer> numss = new ArrayList<>();
		
		res.add(value);
		res.add(count);
		res.add(left);
		res.add(right);
		res.add(numss);
		
		int pre = nums[0];
		value.add(pre);
		left.add(0);
		numss.add(1);
		int k = 1;
		int seg = 1;
		int l = 0;
		for(int i = 1;i<nums.length;i++){
			if(pre!=nums[i]){
				value.add(nums[i]);
				pre = nums[i];
				count.add(k);
				k = 1;
				left.add(i);
				l = i;
				int p = left.size()-1;
				while(right.size()<p){
					right.add(i-1);
				}
				numss.add(++seg);
			}else{
				numss.add(seg);
				k++;
				left.add(l);
			}
		}
		right.add(nums.length-1);
		count.add(k);
		return res;
	}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值