【BZOJ2221】面试的考验,随机数列+线段树+离线

传送门
shallwe’s blog //以上内容由shallwe本人暴力添加
思路:
好题
断断续续想了1周左右
暴力思路显然是 O(n2logn)
我最开始的想法是用莫队+set来搞
但是发现即使是在随机数列下, O(nnlogn) 的复杂度也是不科学的
那怎么办?
(下列做法可能比较难想……)
如果说我们处理出当前区间中每个元素往前的最小差值,那不就可以得到答案了吗?
再深入思考一下
对于 a[j]>a[k]>x , j<k , a[j]>a[k]>x,j<k a[j] 不贡献答案;
a[j]<a[k]<x 时同理
那么对于每一个元素x来说,我们只要在前面找小于a[x]的递增序列和大于x的递减序列就可以了
因为只有它们才会在x存在时贡献答案
然后我们这里引入一个奥妙重重的性质

对于长度为n的随机数列中的某个元素a[x],下标小于x的数构成的小(大)于a[x]的递减(增)序列长度为 logn

对于这道题来说,这个性质是极好的
这样就相当于每次找某个权值区间中位置小于某个值的最大位置了
但这样并不好找
因为当前点后面的数会对我们查询产生影响
那就从左向右扫着做,后面数就不会有影响了
这样就相当于找某个权值区间中的最大位置了
这就很好办了啊
权值线段树就可以了
操作是单点修改和区间求max
那每次求出的答案的贡献区间是什么呢?
如果说当前点为r,找到的贡献点是l,那么它对答案的贡献区间是左端点在 [1,l] ,右端点在 [r,n] 的询问
这不是很好维护
所以我们干脆把询问离线下来,以右端点为关键字从小到大排序,然后把所有的答案都记到左端点上,这样就可以拿一颗新的线段树来维护了
每次修改的区间就是 [1,l]
操作是区间修改和区间求min
总结一下就是随机数列的神奇性质+离线+扫描线思想+双线段树维护
代码还是挺好写的
题解好难写啊
代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define M 100005
#define up 2147483647
using namespace std;
int n,m;
int a[M],b[M],ha[M],S[M<<2],V[M<<2],lazy[M<<2],ans[M];
struct query
{
    int id,l,r;
}q[M];
int in()
{
    int t=0;char ch=getchar();bool f=0;
    while (ch>'9'||ch<'0') f=(ch=='-'),ch=getchar();
    while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar();
    return f?-t:t;
}
bool cmp(query a,query b)
{
    return a.r<b.r;
}
int V_get(int rt,int begin,int end,int l,int r)
{
    if (l<=begin&&end<=r) return V[rt];
    int mid=begin+end>>1,ans=0;
    if (mid>=l) ans=max(ans,V_get(rt<<1,begin,mid,l,r));
    if (mid<r)  ans=max(ans,V_get(rt<<1|1,mid+1,end,l,r));
    return ans;
}
void V_update(int rt,int begin,int end,int pos,int val)
{
    if (begin==end) return void(V[rt]=val);
    int mid=begin+end>>1;
    if (mid>=pos) V_update(rt<<1,begin,mid,pos,val);
    else V_update(rt<<1|1,mid+1,end,pos,val);
    V[rt]=val;
}
void S_build(int rt,int begin,int end)
{
    S[rt]=up;
    if (begin==end) return;
    int mid=begin+end>>1;
    S_build(rt<<1,begin,mid);
    S_build(rt<<1|1,mid+1,end);
}
void pushdown(int rt)
{
    if (!lazy[rt]) return;
    S[rt<<1]=min(S[rt<<1],lazy[rt]);
        if (!lazy[rt<<1]) lazy[rt<<1]=lazy[rt];
        else lazy[rt<<1]=min(lazy[rt],lazy[rt<<1]);
    S[rt<<1|1]=min(S[rt<<1|1],lazy[rt]);
        if (!lazy[rt<<1|1]) lazy[rt<<1|1]=lazy[rt];
        else lazy[rt<<1|1]=min(lazy[rt],lazy[rt<<1|1]);
    lazy[rt]=0;
}
void S_update(int rt,int begin,int end,int l,int r,int val)
{
    if (l<=begin&&end<=r)
    {
        S[rt]=min(S[rt],val);
        if (!lazy[rt]) lazy[rt]=val;
        else lazy[rt]=min(val,lazy[rt]);
        return;
    }
    pushdown(rt);
    int mid=begin+end>>1;
    if (mid>=l) S_update(rt<<1,begin,mid,l,r,val);
    if (mid<r) S_update(rt<<1|1,mid+1,end,l,r,val);
    S[rt]=min(S[rt<<1],S[rt<<1|1]);
}
int S_get(int rt,int begin,int end,int l,int r)
{
    if (l<=begin&&end<=r) return S[rt];
    pushdown(rt);
    int mid=begin+end>>1,ans=up;
    if (mid>=l) ans=min(ans,S_get(rt<<1,begin,mid,l,r));
    if (mid<r) ans=min(ans,S_get(rt<<1|1,mid+1,end,l,r));
    return ans;
}
void cal(int x)
{
    int y=1,val=b[0],ans=up;
    for (;val>a[x]&&y;)
    {
        y=V_get(1,1,b[0],a[x],val);
        if (y&&a[y]!=a[x])
            ans=min(ans,ha[a[y]]-ha[a[x]]),
            S_update(1,1,n,1,y,ha[a[y]]-ha[a[x]]);
        if (!y) break;
        val=a[y]-1;
    }
    val=1;y=1;
    for (;val<a[x]&&y;)
    {
        y=V_get(1,1,b[0],val,a[x]);
        if (y&&a[y]!=a[x])
            ans=min(ans,ha[a[x]]-ha[a[y]]),
            S_update(1,1,n,1,y,ha[a[x]]-ha[a[y]]);
        if (!y) break;
        val=a[y]+1;
    }
    V_update(1,1,b[0],a[x],x);
}
main()
{
    n=in();m=in();
    for (int i=1;i<=n;++i) b[i]=a[i]=in();
    sort(b+1,b+n+1);
    b[0]=unique(b+1,b+n+1)-b-1;
    for (int i=1;i<=b[0];++i) ha[i]=b[i];
    for (int i=1;i<=n;++i)
        a[i]=lower_bound(b+1,b+b[0]+1,a[i])-b;
    for (int i=1;i<=m;++i)
        q[i]=(query){i,in(),in()};
    sort(q+1,q+m+1,cmp);
    int last=0;
    S_build(1,1,n);
    for (int i=1;i<=m;++i)
    {
        for (int j=last+1;j<=q[i].r;++j) cal(j);
        ans[q[i].id]=S_get(1,1,n,q[i].l,q[i].r);
        last=q[i].r;
    }
    for (int i=1;i<=m;++i) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值