传送门
题意:给定长度为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;
}