用分块思想解决区间最值差问题

一 问题描述

每天挤奶时,约翰的 N 头奶牛都以相同的顺序排队,他挑选一系列连续的奶牛来做游戏。为了让所有的奶牛都开心,他们的高度差异不应该太大。约翰列出的 Q 组奶牛和他们的高度,他希望确定每个小组中最高和最矮的奶牛之间的高度差异。

二 输入和输出 

1 输入

第 1 行包含两个整数,N 和 Q,接下来的 N 行。每行包含一个整数,表示奶牛的高度,最后 Q 行,每行都包含两个整数 A 和 B,代表从 A 到 B 的奶牛范围。

2 输出

输出 Q 行,每行都包含一个整数,表示该范围内最高和最矮奶牛的高度差。

三 输入和输出样例

1 输入样例

6 3

1

7

3

4

2

5

1 5

4 6

2 2

2 输出样例

6

3

0

四 分析和设计

1 分析

本问题是典型的区间最值查询问题,可采用分块解决。

2 设计

a 分块

划分块,记录每个元素的所属块,每块的左右下标、最大值和最小值。

b 查询

查询区间[l, r] 区间最大值和最小值的差值

• 如果该区间属于同一块,则暴力统计最大值和最小值,返回两者差值;

• 如果该区间包含多块,则统计中间每一块的最大值和最小值,然后暴力统计左端和右端的最大值和最小值,返回两者差值。

五 代码

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

import static java.lang.Integer.max;
import static java.lang.Integer.min;

public class Poj3264Block {
    public final int inf = 0x3f3f3f3f;
    public final int maxn = 50010;
    int L[] = new int[maxn];
    int R[] = new int[maxn];
    int belong[] = new int[maxn];
    int block_max[] = new int[maxn];
    int block_min[] = new int[maxn];
    int a[] = new int[maxn];
    int n;
    int m;

    public String output = "";

    void build() {
        int t = (int) Math.sqrt(n);
        int num = n / t;
        if (n % num != 0) num++;
        for (int i = 1; i <= num; i++) {
            L[i] = (i - 1) * t + 1;
            R[i] = i * t;
        }

        R[num] = n;
        for (int i = 1; i <= n; i++)
            belong[i] = (i - 1) / t + 1;
        // 求每块最值
        for (int i = 1; i <= num; i++) {
            int MIN = inf, MAX = -inf;
            for (int j = L[i]; j <= R[i]; j++) {
                MAX = max(MAX, a[j]);
                MIN = min(MIN, a[j]);
            }
            block_max[i] = MAX;
            block_min[i] = MIN;
        }
    }

    int query(int l, int r) {
        int MIN = inf, MAX = -inf;
        if (belong[l] == belong[r]) {
            for (int i = l; i <= r; i++) {
                MAX = max(MAX, a[i]);
                MIN = min(MIN, a[i]);
            }
            return MAX - MIN;
        } else {
            for (int i = l; i <= R[belong[l]]; i++) {//左端
                MAX = max(MAX, a[i]);
                MIN = min(MIN, a[i]);
            }
            for (int i = belong[l] + 1; i < belong[r]; i++) {//中间
                MAX = max(MAX, block_max[i]);
                MIN = min(MIN, block_min[i]);
            }
            for (int i = L[belong[r]]; i <= r; i++) {//右端
                MAX = max(MAX, a[i]);
                MIN = min(MIN, a[i]);
            }
        }
        return MAX - MIN;
    }

    public String cal(String input) {
        int l, r;
        String[] line = input.split("\n");
        String[] num = line[0].split(" ");
        n = Integer.parseInt(num[0]);
        m = Integer.parseInt(num[1]);

        // 下标从1开始
        for (int i = 1; i <= n; i++)
            a[i] = Integer.parseInt(line[i]);
        build();
        for (int j = 1; j <= m; j++) {
            String[] command = line[n + j].split(" ");
            l = Integer.parseInt(command[0]);
            r = Integer.parseInt(command[1]);
            output += query(l, r) + "\n";
        }
        return output;
    }
}

六 测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值