hdu6701 Make Rounddog Happy(笛卡尔树启发式分治)

题目链接、

 

题意:

求有多少对<l,r>,使得[l,r]内无重复数字,且max(a[l],a[l+1]...a[r])-(r-l+1)\leqslant k

题解:

预处理a[i]的管辖区间[\ Lc[i],Rc[i]\ ]

处理一颗笛卡尔树,遍历整棵树,启发式分治区间

 

一、

对于Lc[i],我们先处理一个a[i]上次出现的位置+1,然后取一个前缀max

对于Rc[i],我们先处理一个a[i]上次出现的位置-1,然后取一个后缀min

这想法个来自这里、

二、直接暴力跑区间

1、设一个r,不断往右走,一边走一边标记vis[a[i]]=1

当遇到标记时,记录当前位置-1,即Rc[i]应该在的位置

并清空标记

2、设一个l,不断往左走,一边走一边标记vis[a[i]]=1

当遇到标记时,记录当前位置+1,即Lc[i]应该在的位置

并清空标记

这个想法来自这里、

 

int r = 2;
vis[a[1]] = 1;
for(int i = 1; i <= n; ++i) {
    while(r <= n && !vis[a[r]]) {
        vis[a[r]] = 1;
        ++r;
    }
    vis[a[i]] = 0;
    R[i] = r - 1;
}
vis[a[n]] = 1;
int l = n - 1;
for(int i = n; i >= 1; --i) {
    while(l >= 1 && !vis[a[l]]) {
        vis[a[l]] = 1;
        --l;
    }
    vis[a[i]] = 0;
    L[i] = l + 1;
}

 

考虑求答案,即整个的贡献的时候,注意到我们需要的是

当前整个区间最大值的所在位置,然后递归左右区间,

这和笛卡尔树的定义一样,

笛卡尔树根root,表示的是整个区间最大值的位置所在下标

左儿子l[root]则是左区间[1,root-1]的最大值位置

右儿子r[root]则是右区间[root+1,r]的最大值位置

笛卡尔树

单调栈建树时间复杂度O(n),感觉比求一个st表的常数小得多

(实际上也的确如此,我这份随意写的菜鸡代码溜到了提交第一页的最后一名)

 

 

/*author:revolIA*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+7;
int cnt,n,k;
int l[maxn],r[maxn],stl[maxn],top,root;//tree
int val[maxn],vis[maxn],Lc[maxn],Rc[maxn];
ll ans;
void dfs(int L,int R,int mid){
    if(L>R)return;
    int len = val[mid]-k;
    if(mid-L<R-mid){
        for(int i=mid;i>=L;i--){//枚举L
            int tl = max(mid,i+len-1);//计算r、即r >= val[mid] - k + L -1
            int tr = min(Rc[i],R);//合法区间
            if(tl <= tr)ans += tr-tl+1;//有多少个在合法区间内
        }
    }else{
        for(int i=mid;i<=R;i++){//同理
            int tl = max(L,Lc[i]);
            int tr = min(i-len+1,mid);
            if(tl <= tr)ans += tr-tl+1;
        }
    }
    dfs(L,mid-1,l[mid]);
    dfs(mid+1,R,r[mid]);
}
int main(){
    int T;
    for(scanf("%d",&T);T--;){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++){
            scanf("%d",&val[i]);
            //insert i
            while(top && val[stl[top]] < val[i])l[i] = stl[top--];
            if(top)r[stl[top]] = i;
            stl[++top] = i;
        }
        while(top)root = stl[top--];

        for(int i=1;i<=n;i++)vis[i] = 0;
        for(int i=1;i<=n;i++)Lc[i] = vis[val[i]]+1,vis[val[i]] = i;
        for(int i=2;i<=n;i++)Lc[i] = max(Lc[i],Lc[i-1]);

        for(int i=n;i>=1;i--)vis[i] = n+1;
        for(int i=n;i>=1;i--)Rc[i] = vis[val[i]]-1,vis[val[i]] = i;
        for(int i=n;i>=2;i--)Rc[i-1] = min(Rc[i-1],Rc[i]);

        ans = 0;
        dfs(1,n,root);
        printf("%d\n",ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值