[JZOJ1045][GDOI2005]河床

Description
  地理学家们经常要对一段河流进行测量分析。他们从上游开始向下游方向等距离地选择n(<=30000)个点测量水位深度。得到一组数据d1,d2,…,dn,回到实验室后数据分析员根据需要对数据进行分析,发掘隐藏在数据背后的规律。最近,乌龙博士发现某种水文现象与河床地势有关,于是他指示他手下的分析员要找出一段河流中最大高低起伏差不超过k(k<=100)的最长一段。这看似一个复杂的问题,由于任务紧急,分析员来求助于你,并告诉你博士的所有数据都精确到个位。
  
Input
  输入文件有两行:第一行是整数n,k,分别表示测量点的个数和博士要求的最大水深差(也就是河床地势差)。第二行有n个整数,表示从上游开始依次得到的水位深度 di(1<=i<=n,0<=di<=32767)。

Sample Input
6 2
5 3 2 2 4 5

Sample Output
4

Hint
  提示:从第二个测量点到第五个测量点之间的那段:3 2 2 4,他们起伏最大是4-2=2。

看到题目首先想到二分。
二分什么呢?直接二分答案,也就是长度。
二分出的答案m,如何判断是否合法呢?
对于二分出来的答案m,我们枚举每一个长度为m的区间,如果其中有一个区间存在最大减最小<=k,也就是不超过k,说明长度m合法。
但是求区间[l,r]的最值是O(n)的,如何优化?
1.线段树优化,在O(logn)时间求区间最值。
2.RMQ算法优化,以O(nlogn)时间的预处理来O(1)求最值。

这里提供RMQ算法的代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>

const int N = 30007;

int mi[N][20], mx[N][20], a[N], lg[N];

int n, k;

inline int min(int x, int y) { return x < y ? x : y; } //自定义函数比STL的更快
inline int max(int x, int y) { return x > y ? x : y; }

void init() //读入
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", a + i);
        mi[i][0] = mx[i][0] = a[i]; //初始化f[i][0]
    }
}

void RMQ()
{
    lg[1] = 0;
    for (int i = 2; i <= n; i++) //线性求对数
        lg[i] = lg[i >> 1] + 1;

    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++) //这个地方要保证数组不越界
        {
            mi[i][j] = min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
            mx[i][j] = max(mx[i][j - 1], mx[i + (1 << (j - 1))][j - 1]);
        } //递推
}

inline int qry_min(int l, int r) //回答区间最小
{
    int len = r - l + 1;
    return min(mi[l][lg[len]], mi[r - (1 << lg[len]) + 1][lg[len]]); //RMQ过程,重叠区间
}

inline int qry_max(int l, int r) //回答区间最大
{
    int len = r - l + 1;
    return max(mx[l][lg[len]], mx[r - (1 << lg[len]) + 1][lg[len]]); //RMQ过程,重叠区间
}

int check(int m) //检查答案是否合法
{
    for (int i = 1; i <= n - m + 1; i++) //枚举每一个长度为m的区间
        if (qry_max(i, i + m - 1) - qry_min(i, i + m - 1) <= k) //存在一个这样的区间
            return 1;
    return 0;
}

void solve()
{
    RMQ(); //先处理好RMQ数组

    int l = 1, r = n, ans;
    while (l <= r) //二分答案
    {
        int mid = (l + r) >> 1;
        if (check(mid))
            l = mid + 1, ans = mid; //记录答案
        else
            r = mid - 1;
    }

    printf("%d\n", ans); //输出答案
}

int main()
{
    init();
    solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值