杭电多校第十场 Make Rounddog Happy 思维(分治枚举最大值影响区间)

本文解析了HDU 6701题目,通过使用笛卡尔树和分治策略,解决了寻找“好的”子区间的算法问题。介绍了如何利用笛卡尔树确定每个值能影响的区间,并通过优化复杂度实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6701

题意:

你有一个长为nnn的数组。现在定义一段连续的子区间 [l,r][l,r][l,r] 为“好的”,当且仅当max(al,al+1,…,ar)−(r−l+1)≤kmax(a_l,a_{l+1},…,a_r)−(r−l+1)≤kmax(al,al+1,,ar)(rl+1)k ,并且这个区间中没有重复的值,问你能找到多少个"好的"子区间。

做法:

有种经典套路的感觉 (做题不多的我最近好像碰到蛮多这样的题)。我们枚举自己作为最大值可以影响的区间,即对每一个值我们都求出它作为最大值可以影响到的最远的左端和右端。这个操作可以用笛卡尔树来解决(即一个手写的O(n)O(n)O(n)最大(小)堆),如果不知道笛卡尔树是什么的…找大佬的博客叭。

然后我们就要开始分治了,对于每个最大值,我们找这个合法区间中左边和右边元素比较少的一侧进行枚举,注意是元素比较少的一侧,这个优化可以把最坏情况下O(n2)O(n^2)O(n2)的复杂度优化到O(nlog2n)O(nlog_2n)O(nlog2n)。 想象一下,不优化的话,最坏情况就是最值每次都在角落,然后枚举的位置总是多的一侧,这样就是O(n2)O(n^2)O(n2)的,但是如果枚举少的,那么每次最值都在中间的时候才会出现最坏情况O(nlog2n)O(nlog_2n)O(nlog2n)。 然后我们枚举少的那一侧,复杂度就是O(n(log2n)2)O(n(log_2n)^2)O(n(log2n)2)。在这道题中是可以接受的。

我们在进行分治之前,还需要做一个处理,即对每一个位置,找出以它为左端无重复元素的右端,和以它为右端无重复元素的左端。这个东西可以保证我们在枚举的时候对于每个元素只需要O(1)O(1)O(1)的时间就能算出答案,这个左右端用尺取就能做到。在这里还要对情况进行讨论,如果这个区间超过了我们分治的区间,那么要压缩,还有其他的细节就看代码叭。

代码

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int maxn=300005;
int a[maxn],sta[maxn],rson[maxn];
int vis[maxn],lson[maxn],top,n,k;
int L[maxn],R[maxn],num[maxn];
ll ans;
void dfs(int l,int r,int rt){
    if(r<l)return ;
    int nma=a[rt],minl=a[rt]-k;
    if(r-rt>rt-l){//右边比左边大 for左边
        for(int i=l;i<=rt;i++){
            if(L[i]<rt||min(L[i],r)-i+1<minl) continue;
            ans+=(ll)(min(L[i],r)-max(i+minl-1,rt)+1);
        }
    }
    else{//左边比右边大 for右边
        for(int i=r;i>=rt;i--){
            if(R[i]>rt||i-max(R[i],l)+1<minl) continue;
            ans+=(ll)(min(i-minl+1,rt)-max(R[i],l)+1);
        }
    }
    dfs(l,rt-1,lson[rt]);
    dfs(rt+1,r,rson[rt]);
}
int main(){
    /*freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);*/
    int T; scanf("%d",&T);
    while (T--){
        scanf("%d%d",&n,&k);
        top=0;
        rep(i,1,n){
            scanf("%d",&a[i]);
            vis[i]=false;
            rson[i]=lson[i]=0;
            L[i]=R[i]=num[i]=0;
        }
        int r=0,l=n+1;
        rep(i,1,n){
            while(r<n&&num[a[r+1]]==0){
                r++; num[a[r]]++;
            }
            L[i]=r;
            num[a[i]]--;
        }

        for(int i=n;i>=1;i--){
            while(l>1&&num[a[l-1]]==0){
                l--; num[a[l]]++;
            }
            R[i]=l;
            num[a[i]]--;
        }
        rep(i,1,n){
            int tmp=top,flag=0;
            while(tmp&&a[sta[tmp]]<=a[i]) {
                flag=sta[tmp];
                tmp--;
            }
            if(tmp) rson[sta[tmp]]=i;
            if(top>tmp) lson[i]=flag;
            sta[++tmp]=i;
            top=tmp;
        }
        for(int i=1;i<=n;i++) vis[lson[i]]=vis[rson[i]]=true;
        int rt=1;
        while(vis[rt]==true) rt++;
        ans=0;
        dfs(1,n,rt);
        printf("%lld\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值