2015多校联合第一场5289Assignment RMQ+二分

题意:(看英文真费劲==) 给定一数列 求子区间能够满足其中最大值-最小值差<k  之前拿到题就想到线段树 确实数据小点儿能做 搜到了特别优雅的RMQ算法*^_^*

此算法用于求固定区间的最值 本质是动态规划和枚举

2015多校联合第一场5289Assignment RMQ+二分_第1张图片

(一)首先是预处理,用动态规划(DP)解决。

设A[i]是要求区间最值的数列,F[i, j]表示从第i个数起连续2^j个数中的最大值。(DP的状态)

例如:

A数列为:3 2 4 5 6 8 1 2 9 7

F[1,0]表示第1个数起,长度为2^0=1的最大值,其实就是3这个数。同理 F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

并且我们可以容易的看出F[i,0]就等于A[i]。(DP的初始值)

这样,DP的状态、初值都已经有了,剩下的就是状态转移方程。

我们把F[i,j]平均分成两段(因为f[i,j]一定是偶数个数字),从 i 到i + 2 ^ (j - 1) - 1为一段,i + 2 ^ (j - 1)到i + 2 ^ j - 1为一段(长度都为2 ^ (j - 1))。用上例说明,当i=1,j=3时就是3,2,4,5 和 6,8,1,2这两段。F[i,j]就是这两段各自最大值中的最大值。于是我们得到了状态转移方程F[i, j]=max(F[i,j-1], F[i + 2^(j-1),j-1])。

(二)然后是查询。

假如我们需要查询的区间为(i,j),那么我们需要找到覆盖这个闭区间(左边界取i,右边界取j)的最小幂(可以重复,比如查询5,6,7,8,9,我们可以查询5678和6789)。

因为这个区间的长度为j - i + 1,所以我们可以取k=log2( j - i + 1),则有:RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}。

举例说明,要求区间[2,8]的最大值,k = log2(8 - 2 + 1)= 2,即求max(F[2, 2],F[8 - 2 ^ 2 + 1, 2]) = max(F[2, 2],F[5, 2]);


在这里我们也需要注意一个地方,就是<<运算符和+-运算符的优先级。



本题:枚举右端点,很明显 区间越大,最大小值差越大,所以有线性关系。所以可以二分。找到差值小于k的点,这个点到右端点之间所有点都可以做为左端点。

#include <iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int A[100100],FMIN[100100][20],FMAX[100100][20];
int t,n,k;
void init()
{
    for(int i=1;i<=n;i++) FMIN[i][0]=FMAX[i][0]=A[i];
    for(int i=1;(1<<i)<=n;i++)
        for(int j=1;j+(1<<i)-1<=n;j++)
        {
            FMIN[j][i]=min(FMIN[j][i-1],FMIN[j+(1<<i-1)][i-1]);
            FMAX[j][i]=max(FMAX[j][i-1],FMAX[j+(1<<i-1)][i-1]);
        }
}
int query(int l,int r)
{
    int k=(int)(log(double(r-l+1))/log((double)2));
    return max(FMAX[l][k],FMAX[r-(1<<k)+1][k]);
}
int query2(int l,int r)
{
    int k=(int)(log(double(r-l+1))/log((double)2));
    return min(FMIN[l][k],FMIN[r-(1<<k)+1][k]);
}
int main()
{
   // freopen("cin.txt","r",stdin);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) scanf("%d",&A[i]);
        init();
        __int64 ans=0;
        int lll=1;
        for(int i=1;i<=n;i++)
        {
            int l=lll,r=i;
            while(l<=r)
            {
                int mid=(l+r)/2;
                int low=query2(mid,i);
                int hig=query(mid,i);
                int tt=hig-low;
                if(tt>=k) l=mid+1;
                else r=mid-1;
            }
            lll=l;
            ans+=i-l+1;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(算法,HDU,多校)