VK Cup 2015 - Qualification Round 1 D. Closest Equals (线段树离线 / 主席树)

传送门

题意:给定长度为n的序列,q次询问,每次询问[l,r]区间内满足a[x] == a[y]最近的x y,定义距离为|x-y|,输出这个距离。如果不存在两个相同的元素则输出-1.
不强制在线。
(1<=n,q<=5e5)

分析:

  • 首先我们考虑对于右端点固定的区间[1,r],如何用线段树进行查询?我们可以用p[i]表示a[i]上一次最近出现的位置。

  • 求出p数组后,如果我们让v[i]表示|i-p[i]|,那么会出现一个情况就是询问[l,r]区间的时候,某个数的p[]在l左边,但是影响了答案。所以我们换个映射位置,将|i-p[i]|映射到p[i]的位置,即:v[p[i]]=|i-p[i]|

  • 这样v[i]的含义就变成了,a[i]与后一位的距离,用线段树维护v[]数组的最小值即可。

  • 以上是右端点固定的做法,不固定的话,我们可以对每个前缀建树(建主席树),每次取出root[r]版本的线段树进行查询即可。也可以把查询区间按右端点排序,离线查询。

  • 建主席树的细节:
    ①由于要维护最小值,我们首先tree[0]=INF把虚点都设成INF
    ②空间开到5e5*40

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 5e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int INF = 1e9+7;
map<int,int> mp;//用于记录元素上一次出现的位置
int n,m;
int p[maxn];//p[i]:a[i]上一次出现的位置
int tree[maxn*mx],root[maxn],lc[maxn*mx],rc[maxn*mx],cnt;//主席树
inline void updata(int &rt,int last,int l,int r,int pos,int v)
{
    rt=++cnt;
    tree[rt]=tree[last];
    lc[rt]=lc[last],rc[rt]=rc[last];
    if(l == r) 
    {
        tree[rt]=v;
        return ;
    }
    int mid=l+r>>1;
    if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos,v);
    else updata(rc[rt],rc[last],mid+1,r,pos,v);
    tree[rt]=min(tree[lc[rt]] , tree[rc[rt]]);
}
inline int query(int rt,int l,int r,int vl,int vr)
{
    if(vl<=l && r<=vr) return tree[rt];
    int mid=l+r>>1;
    if(vr<=mid) return query(lc[rt],l,mid,vl,vr);
    if(vl>mid) return query(rc[rt],mid+1,r,vl,vr);
    return min(query(lc[rt],l,mid,vl,vr),query(rc[rt],mid+1,r,vl,vr));
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        if(mp.count(x)) p[i]=mp[x];
        mp[x]=i;
    }
    tree[0]=INF;//初始化 没有开的点全部是INF
    for(int i=1;i<=n;i++) 
    {
        root[i]=root[i-1];
        if(p[i]) updata(root[i],root[i-1],1,n,p[i],i-p[i]);
    }
    for(int i=1;i<=m;i++)
    {
        int l,r;scanf("%d %d",&l,&r);
        int ans=query(root[r],1,n,l,r);
        if(ans==INF) ans=-1;
        printf("%d\n",ans);
    }
    return 0;
}

离线做法:区间按右端点从小到大排序。
枚举区间,线段树从1开始更新,更新到当前区间的右端点。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 5e5+10;
const int mx = 40;
const int mod = 1e9+7;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int INF = 1e9+7;
map<int,int> mp;//i上一次出现的位置
int n,m;
int p[maxn];
struct qy
{
    int l,r,id;
    bool operator <(const qy &f) const 
    {
        return r < f.r; 
    }
}q[maxn];
int ans[maxn];
int tree[maxn<<2];//单点更新 区间查询 用i-mp[a[i]]去更新mp[a[i]]  维护最小值
inline int lc(int &rt) {return rt<<1;}
inline int rc(int &rt) {return rt<<1|1;}
inline void pushup(int &rt) {tree[rt]=min(tree[lc(rt)] , tree[rc(rt)]);}
inline void build(int rt,int l,int r)
{
    if(l == r) 
    {
        tree[rt]=INF;
        return ;
    }
    int mid=l+r>>1;
    build(lc(rt),l,mid);
    build(rc(rt),mid+1,r);
    pushup(rt);
}
inline void updata(int rt,int l,int r,int pos,int v)
{
    if(l == r) tree[rt]=min(tree[rt],v);
    else 
    {
        int mid=l+r>>1;
        if(pos<=mid) updata(lc(rt),l,mid,pos,v);
        else updata(rc(rt),mid+1,r,pos,v);
        pushup(rt);
    }
}
inline int query(int rt,int l,int r,int vl,int vr) 
{
    if(vl<=l && r<=vr) return tree[rt];
    int mid=l+r>>1;
    if(vr<=mid) return query(lc(rt),l,mid,vl,vr);
    if(vl>mid) return query(rc(rt),mid+1,r,vl,vr);
    return min(query(lc(rt),l,mid,vl,vr),query(rc(rt),mid+1,r,vl,vr));
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        if(mp.count(x)) 
        {
            p[i]=mp[x];
        }
        mp[x]=i;
    }
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q+1,q+1+m);
    int r=1;//右端点
    for(int i=1;i<=m;i++) 
    {
        //把q[i]右端点更新完
        while(r<=q[i].r) 
        {
            if(p[r])
                updata(1,1,n,p[r],r-p[r]);
            r++;
        }
        //此时[1,r] i表示suf[i]-i  suf[i]表示a[i]后一次出现的位置
        //注意区间查询的范围是[l,r-1] 此时q[i].r==r-1
        ans[q[i].id]=query(1,1,n,q[i].l,r-1);       
    }
    for(int i=1;i<=m;i++) 
    {
        if(ans[i]==INF) ans[i]=-1;
        printf("%d\n",ans[i]);
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值