问题描述
区间Mex问题,简单说来,就是要查询区间最小未出现正整数(非负整数和正整数可以通过整体加减一相互转换,这里不作区分了)。
详见 Rmq Problem / mex 。
离线区间Mex问题,用莫队算法 O ( n n ) O(n\sqrt n) O(nn) 的求法已经很成型了;不过有一个离线情况下更好的方法。
我们首先考虑数字上限不是很大,不需要离散化的情况:
首先离线情况下,可以把查询按照右端点 R R R 排序。
之后可以随着右端点增加,逐个添加数据。我们的问题就变成了:对于当前的状态,所有数最后一次出现的位置中,比我所查询的左端点 L L L 小的最小的数字。
因此,我们考虑维护一棵 权值线段树,维护每个数字最后一次出现的位置,通过线段树维护位置的最小值。
查询时,给出 L L L ,如果 L L L 小于等整棵树的最小值,证明所有编号都出现过;之后在线段树上深搜,如果 L L L 大等于左子树的最小值,就去往左子树,否则去往右子树,直到到达叶子节点,这个节点代表的数就是我要找的最小未出现的数。
之后是 离散化 的处理办法:
处理办法是,在离散化的过程中,加入每个数+1 。那么效果就是,原来有两个不挨着的数因为离散化而挨在一起了;处理后他们就不会挨在一起,并且他们之间会隔着一个永远不会在序列中出现的数字。
仔细一想,你就会发现离散化的问题解决了。现在查到的结果作为排名找到原来的数字,结果就是正确的。
用到的算法和数据结构知识都比较基础(甚至比莫队还基础),整个过程中不论是排序、离散化、还是线段树,复杂度都是 O ( n l o g n ) O(nlogn) O(nlogn) ,因此总的复杂度也是 O ( n l o g n ) O(nlogn) O(nlogn) 。
附代码,如有错误,恳请指正:
#include <iostream>
#include <algorithm>
using namespace std;
const int maxTreeNode=1000000, maxn=200001;
class segmentTree{
public:
segmentTree(const int limit):
limit(limit), root(build(1, limit)){}
int min()
{
return mi[root];
}
int ask(int x)
{
return ask(root, 1, limit, x);
}
void update(int d, int v)
{
update(root, 1, limit, d, v);
return;
}
private:
int mi[maxTreeNode], son[2][maxTreeNode], totPoint;
const int limit, root;
int build(int l, int r)
{
int cur=++totPoint;
if(l==r) return cur;
int mid=l+r>>1;
son[0][cur]=build(l, mid);
son[1][cur]=build(mid+1, r);
return cur;
}
void update(int k, int l, int r, int d, int v)
{
if(l==r){
mi[k]=v;
return;
}
int mid=l+r>>1;
if(d<=mid) update(son[0][k], l, mid, d, v);
else update(son[1][k], mid+1, r, d, v);
mi[k]=std::min(mi[son[0][k]], mi[son[1][k]]);
return;
}
int ask(int k, int l, int r, int x)
{
if(l==r) return l;
int mid=l+r>>1;
if(mi[son[0][k]]<x) return ask(son[0][k], l, mid, x);
else return ask(son[1][k], mid+1, r, x);
}
}; //线段树
struct query{
int l, r, id;
bool operator<(const query& another)const {
return r==another.r ? l<another.l : r<another.r;
}
}q[maxn]; //离线
int a[maxn], ans[maxn], cpy[maxn<<1], cnt;
int getRnk(int x)
{
return lower_bound(cpy+1, cpy+cnt+1, x)-cpy;
}
int getNum(int k)
{
return cpy[k];
}
int main()
{
//freopen("test.in", "r", stdin);
int n, m, l;
cin >> n >> m;
cpy[++cnt]=1; //放入1,作为下界
for(int i=1; i<=n; i++)
cin >> a[i], cpy[++cnt]=a[i], cpy[++cnt]=a[i]+1;
for(int i=1; i<=m; i++)
cin>> q[i].l >> q[i].r, q[i].id=i;
sort(q+1, q+m+1);
sort(cpy+1, cpy+cnt+1);
cnt = unique(cpy+1, cpy+cnt+1)-cpy; //去重
for(int i=1; i<=n; i++)
a[i]=getRnk(a[i]); //离散化,用排名代替数据
segmentTree* Tree=new segmentTree(cnt);
int cur=0;
for(int i=1; i<=m; i++){
while(cur<q[i].r)
Tree->update(a[cur], ++cur);
ans[q[i].id]=Tree->ask(q[i].l);
}
for(int i=1; i<=m; i++)
cout<<getNum(ans[i])<<endl;
return 0;
}
update:
根据队友的指正,实际上不需要把每个数字+1都放进离散化序列之中,因为有可能成为mex的、原序列中没有出现过的数,只可能是把原序列排序从1开始连续数,得到的最小的未出现的数字。
因此对离散化部分做出如下更新:
for(int i=1; i<=n; i++)
cin >> a[i], cpy[++cnt]=a[i], exist[a[i]]=true; //n是1e5量级的,所以最小未出现的数字也在1e5以内。
int index=0;
while(exist[index]) index++;
cpy[++cnt]=index;
如有错误,恳请指正。