一.题意
一道比较综合的RMQ(倍增)问题。
给你 \(n\) 个数 \(a_1,a_2,...,a_n\),\(m\) 次询问,每次给定一个区间 \(l,r\) ,让你求出最长的一段序列,满足这个序列中的数各不相同,输出这段序列的长度。
对于全部数据,\(1\le N,M\le 2\times 10^5\) , \(0\le L\le R\le N-1\) , \(|a_i|\le 10^6\)
二.思路
- 首先我们令 \(sti\) 表示以 \(i\) 这个下标 结尾的最长完美序列的开始位置,\(sti=max(sti-1,lsta\[i]+1)\)
- 我们令 \(lsti\) 表示 \(i\) 这个数 最后一次出现的下标,\(lsti=lsta\[i]\)
- 我们令 \(fi\) 表示以 \(i\) 这个下标 结尾的最长完美序列的长度,\(fi=i-sti+1\)
- \(st,lst,f\) 都可以在 \(O(n)\) 的时间复杂度下完成
不难发现 \(st\) 是一个非降的序列,那么对于每一次 \(l,r\) 的区间询问中,\(st\) 的值可能出现两种情况
- 左边的一部分 \(st\) 值不属于所求的区间内,即小于 \(l\)
- 右边的一部分 \(st\) 的值一直大于等于 \(l\) ,由于 \(st\) 数组内元素具有单调性,所以我们不难想到用二分找出这个分界点 \(mid\)
经过上述的预处理后,则要求的 \(l,r\) 的完美序列长度可以分成两部分解决
- 左边的最大长度即为 \(mid-l+1\)
- 右边的最大长度即为 \(mid+1,r\) 这个区间内 \(f\) 数组的最大值
注意到,\(mid+1,r\) 这个区间的最大值可以用 ST 表进行维护 (ST表不会的可以参考这里)
三.实现代码
这个代码的实现细节还挺多的
- \(ai\) 的值可能是负数 ,会导致 \(lst\) 数组下标访问越界,所以我们需要将所有 \(ai\) 加上一个偏移值 \(10^6\)
- \(lst\)数组存的是某个数 在序列中最后一次出现的下标,所以数组要开到 \(2\times 10^6\)
- 要注意考虑二分查找中的边界问题
题目传送门 没想到这题竟然是青题
完整代码
cpp
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N=2e5+5;
const int M=1e6;
int n,q,a[N],lst[2*M+5],st[N],f[N],g[N][19],lg[N];
inline int ask(int l,int r){
if (l>r)return 0;
int p=lg[r-l+1];
return max(g[l][p],g[r-(1<<p)+1][p]);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]+=M;
st[i]=max(st[i-1],lst[a[i]]+1);
lst[a[i]]=i;
g[i][0]=f[i]=i-st[i]+1;
}
lg[2]=1;
for(int i=3;i<=n;i++){
lg[i]=lg[i/2]+1;
}
for(int j=1;j<=18;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
g[i][j]=max(g[i][j-1],g[i+(1<<(j-1))][j-1]);
}
}
while(q--){
int l,r;
cin>>l>>r;
l++,r++;
int left=l,right=r,wz=l;
while(left<=right){
int mid=(left+right)>>1;
if (st[mid]<l){
wz=mid;
left=mid+1;
}else{
right=mid-1;
}
}
int ans=max(wz-l+1,ask(wz+1,r));
cout<<ans<<"\n";
}
return 0;
}