最频繁值问题

一 问题描述

给定 N 个整数的非递减序列a1、a2、...an,对每个索引 i 和 j 组成的查询,都确定整数 ai...aj 中的最频繁值(出现次数最多的值)。

二 输入和输出

1 输入

包含多个测试用例,每个测试用例都以两个整数 n 和 q 的行开始。下一行包括 n 个整数 a1...an。对每个 I,都满足 ai<=ai+1。以下 q 行,每行都包含一个查询,有两个整数 i 和 j 组成,表示查询边界索引。在最后一个测试用例后跟一个包含单个 0 的行。

2 输出

对每个查询,都单含输出一个整数,表示给定范围内最频繁值出现的次数

三 输入和输出样例

1 输入样例

10 3

-1 -1 1 1 1 1 3 10 10 10

2 3

1 10

5 10

0

2 输出样例

1

4

3

四 分析

由于本问题可以将元素的出现次数累计,然后进行区间最值查询,所以可以使用 ST 解决。为提高求 log 的效率,首先用动态规划求出数据范围内所有数的 log 值,将其存储在数组 lb[] 中。使用时查询即可。F[i][j] 表示 [i + 2^j-1] 区间的最大值,区间长度为 2^j。

五 算法设计

1 求出数据范围内所有数的 log 值,将其存储在数组 lb[] 中.

2 非递减序列相等元素一定相邻,将每个元素和前面元素进行比较,将重复次数累计并存入 F[i][0] 中。

3 创建 ST。

4 查询[l,r]区间的最大值。若第 l 个数和前一个数相等,则首先统计第 l 个数在查询区间[l,r]的出现次数,再查询剩余区间的最大值,两者再求最大值。

六 图解

1 求出数据范围内所有数的 log 值,将其存储在数组 lb[] 中。

log[1]=log[0]+1=0

log[2]=log[1]+1=1

log[3]=log[2]=1

log[4]=log[3]+1=2

log[5]=log[4]=2

log[6]=log[5]=2

log[7]=log[6]=2

log[8]=log[7]+1=3

2 将输入样例元素的出现次数累计并存入F[i][0] 中。

3 创建 ST

4 查询 2 3

查询[2,3]区间最频繁值的出现次数。首先 t=l=2,因为 a[2]=a[1],t++,即 t=3;此时 a[3]<>a[2],t-l=1,RMQ(t,r)=RMQ(3,3)=1,求两者的最大值,得到 [2,3]区间最频繁值的出现次数为 1。

5 查询 1 10

查询[1,10]区间最频繁值的出现次数。首先,t=l=1,a[1]<>a[0],t-l=0,RMQ(t,r)=RMQ(1,10)=4,求两者的最大值,得到[1,10]区间最频繁值的出现次数为 4。

6 查询 5 10

查询[5,10]区间最频繁值的出现次数。首先,t=l=5,因为a[5]=a[4],t++,即 t=6;a[6=a[5],t++,即 t=7;此时 a[7]<>a[6],t-l=2,RMQ(t,r)=RMQ(7,10)=3,求两者的最大值,得到[5,10]区间最频繁值的出现次数为 3。

七 代码

package com.platform.modules.alg.alglib.poj3368;

public class Poj3368 {
    public String output = "";

    private final int maxn = 100010;
    int a[] = new int[maxn]; // 数据
    int lb[] = new int[maxn]; // 存储 log 值
    int F[][] = new int[maxn][20]; // F(i,j) 表示区间 [i,i+2^j-1] 的最值,区间长度为 2^j

    void Initlog() {//求解所有log值,保存到数组lb[]
        lb[0] = -1;
        for (int i = 1; i < maxn; i++)
            lb[i] = (i & (i - 1)) > 0 ? lb[i - 1] : lb[i - 1] + 1;
    }

    void ST_create(int n) {//每个测试用例n不同,因此做参数
        for (int j = 1; j <= lb[n]; j++)
            for (int i = 1; i <= n - (1 << j) + 1; i++)//n-2^j+1
                F[i][j] = Math.max(F[i][j - 1], F[i + (1 << (j - 1))][j - 1]);
    }

    int RMQ(int l, int r) {//求区间[l..r]的最值差
        if (l > r) return 0;
        int k = lb[r - l + 1];
        return Math.max(F[l][k], F[r - (1 << k) + 1][k]);
    }

    public String cal(String input) {
        int n, q, l, r;
        Initlog();

        String[] line = input.split("\n");
        String[] words = line[0].split(" ");
        n = Integer.parseInt(words[0]);
        q = Integer.parseInt(words[1]);

        String[] nums = line[1].split(" ");

        for (int i = 1; i <= n; i++) { // 下标从1开始
            a[i] = Integer.parseInt(nums[i - 1]);
            if (i == 1) {
                F[i][0] = 1;
                continue;
            }
            if (a[i] == a[i - 1])
                F[i][0] = F[i - 1][0] + 1;
            else
                F[i][0] = 1;
        }
        ST_create(n);
        for (int j = 1; j <= q; j++) {
            String[] query = line[j + 1].split(" ");
            l = Integer.parseInt(query[0]);
            r = Integer.parseInt(query[1]);


            int t = l;
            while (t <= r && a[t] == a[t - 1])
                t++;
            output += Math.max(t - l, RMQ(t, r)) + "\n";
        }
        return output;
    }
}

八 测试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值