2019 hdu 多校10 Make Rounddog Happy(启发式分治)

链接

题意:

有一个长度为n的数组,每个数的大小不超过n。定义“好区间”:[l,r]内的最大值+len<=k且区间内不能存在相同的数,k是给定的。询问整个数组中“好区间”的数量。



思路:

比赛的时候不知道如何处理区间不存在相同数字,想想主席树好像可以判断一个区间有没有相同的数字,但是没有了下文。
结束后看了别人的博客,区间的最大值可以直接用st表来处理,而处理无重复数字可以记录每个点向左最远可以延申多少,向右最远可以延申多少。对于一个区间,查询st表找出最大值的位置,然后以这个点划分当前的区间,在左右两部分中选择短的那个区间遍历一边的点,计算另一边的有效长度,并计入答案。
选择短的区间,每次都会暴力小于等于一半的区间,感性理解下时间复杂度大约会在On-Onlog,具体证明不会



参考代码:
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
const ll INF = 1e18 + 5;
int n,k;
int a[N],ls[N],sz;
int stmaxs[N][20];
int log_2[N];

void build(){
    for (int i=1;i<=n;i++){
        stmaxs[i][0]=i;
    }
    for (int j=1;(1<<j)<=n;j++)
        for (int i=1;i+(1<<j)-1<=n;i++){
            if(a[stmaxs[i][j-1]]>=a[stmaxs[i+(1<<(j-1))][j-1]]){
                stmaxs[i][j]=stmaxs[i][j-1];
            }
            else {
                stmaxs[i][j]=stmaxs[i+(1<<(j-1))][j-1];
            }
        }
}

int query_max(int l,int r)
{
    int len=log_2[r-l+1];
    if(a[stmaxs[l][len]]>=a[stmaxs[r-(1<<len)+1][len]]){
        return stmaxs[l][len];
    }
    else{
        return stmaxs[r-(1<<len)+1][len];
    }
}

int pst[N];
int hst[N];
int mp[N];
ll ans;

void solve(int l,int r){
    if(l>r)return;
    int mid=query_max(l,r);
    if(mid-l<r-mid){
        for(int i=l;i<=mid;i++){
            int len=ls[a[mid]]-k;
            int L=i+len-1;
            int R=min(r,hst[i]);
            L=max(L,mid);
            if(R>=L){
                ans+=1ll*(R-L+1);
            }
        }
    }
    else {
        for(int i=mid;i<=r;i++){
            int len=ls[a[mid]]-k;
            int R=i-len+1;
            int L=max(l,pst[i]);
            R=min(R,mid);
            if(R>=L){
                ans+=1ll*(R-L+1);
            }
        }
    }
    solve(l,mid-1);
    solve(mid+1,r);
}

int main() {
    log_2[0]=-1;
    for(int i=1;i<N;i++){
        log_2[i]=log_2[i>>1]+1;
    }
    int t;
    scanf("%d", &t);
    for (int ca = 1; ca <= t; ca++) {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            ls[i]=a[i];
        }
        sort(ls+1,ls+1+n);
        sz=unique(ls+1,ls+1+n)-ls-1;
        for(int i=1;i<=n;i++){
            a[i]=lower_bound(ls+1,ls+1+sz,a[i])-ls;
        }
        build();
        for(int i=1;i<=sz;i++){
            mp[i]=0;
        }
        pst[1]=1;
        mp[a[1]]=1;
        for(int i=2;i<=n;i++){
            if(mp[a[i]]){
                pst[i]=max(pst[i-1],mp[a[i]]+1);
            }
            else {
                pst[i]=pst[i-1];
            }
            mp[a[i]]=i;
        }
        for(int i=1;i<=sz;i++){
            mp[i]=0;
        }
        hst[n]=n;
        mp[a[n]]=n;
        for(int i=n-1;i>=1;i--){
            if(mp[a[i]]){
                hst[i]=min(hst[i+1],mp[a[i]]-1);
            }
            else {
                hst[i]=hst[i+1];
            }
            mp[a[i]]=i;
        }
        ans=0;
        solve(1,n);
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值