Problem
给定长度为 n 的序列 a。现有 m 次询问,每次给定 l 和 r,询问区间 [l,r] 的数构成的集合的 mex 值。给定数据类型t,当t=1时强制在线。
Hint
Solution
这道题有点思维难度,所以博主在此写下三种算法。
算法Ⅰ:线段树
70points的数据允许离线,所以我们故意想一想离线。
我们可以将询问按左端点排序,然后依次枚举每个位置作为左端点,预处理出每个位置作为右端点的mex值(即1~任意i的mex值),将其存在一棵线段树里。我们同时预处理出每个数的下一个与其相同的数的位置next[i]。
设当前左端点为i,我们要将其右移一格,其实就相当于令i+1~next[i]-1中所有mex与a[i]取min。利用线段树区间修改即可。
若我们要询问区间[l,r],则当左端点枚举到i时,直接查询线段树中r这个位置的值即可。
时间复杂度:
O((n+m)∗log2n)
O
(
(
n
+
m
)
∗
l
o
g
2
n
)
。期望得分:70。
Code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#define A v<<1
#define B A|1
#define fi first
#define se second
#define mp make_pair
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef pair<int,int> P;
const int N=2e5+1,inf=0x7FFFFFFF;
int i,n,m,a[N],mex[N],next[N],l,r,tp;
map<int,int>last;
vector<P>q[N];
void scan()
{
scanf("%d%d%d",&n,&m,&tp);
fo(i,1,n)
{
scanf("%d",&a[i]);next[last[a[i]]]=i;last[a[i]]=i;
mex[i]=mex[i-1];while(last[mex[i]])mex[i]++;
}
fo(i,1,n)if(!next[i])next[i]=n+1;
fo(i,1,m)
{
scanf("%d%d",&l,&r);
q[l].push_back(mp(i,r));
}
}
int t[N<<2];
void maketree(int v,int l,int r)
{
if(l==r){t[v]=mex[l];return;}
t[v]=inf;int mid=l+r>>1;
maketree(A,l ,mid);
maketree(B,mid+1,r );
}
int x,y,ans[N],val;
vector<P>::iterator it;
inline void push(int v)
{
if(t[v]==inf)return;
t[A]=min(t[A],t[v]);
t[B]=min(t[B],t[v]);
t[v]=inf;
}
int query(int v,int l,int r)
{
if(l==r)return t[v];
push(v);
int mid=l+r>>1;
if(x<=mid)return query(A,l,mid);
return query(B,mid+1,r);
}
void modify(int v,int l,int r)
{
if(r<x||l>y)return;
if(x<=l&&r<=y){t[v]=min(t[v],val);return;}
int mid=l+r>>1;
modify(A,l,mid);modify(B,mid+1,r);
}
void work()
{
fo(l,1,n)
{
for(it=q[l].begin();it!=q[l].end();it++)
{
P t=*it;i=t.fi;x=t.se;
ans[i]=query(1,1,n);
}
x=l+1;y=next[l]-1;val=a[l];
modify(1,1,n);
}
}
void print()
{
fo(i,1,n)printf("%d\n",ans[i]);
}
int main()
{
scan();
maketree(1,1,n);
work();
print();
}
算法Ⅱ:主席树
算法Ⅰ其实可以轻松改为在线:使用主席树。
但这区别于一般的主席树:一般的主席树是对于每个位置i都建一棵存储1~i信息的权值线段树;本做法则是对于每个左端点i都建一棵存储当右端点为i~n时的mex值。这就相当于用主席树做可回溯至历史版本的区间修改、查询一样。
时间复杂度:
O((n+m)∗log2n)
O
(
(
n
+
m
)
∗
l
o
g
2
n
)
。期望得分:100。
Code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#define A t[v].l
#define B t[v].r
#define fi first
#define se second
#define mp make_pair
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef pair<int,int> P;
const int N=2e5+1,inf=0x7FFFFFFF;
int i,n,m,tp,a[N],mex[N],next[N],l,r;
map<int,int>last;
void scan()
{
scanf("%d%d%d",&n,&m,&tp);
fo(i,1,n)
{
scanf("%d",&a[i]);next[last[a[i]]]=i;last[a[i]]=i;
mex[i]=mex[i-1];while(last[mex[i]])mex[i]++;
}
fo(i,1,n)if(!next[i])next[i]=n+1;
}
int x,y,rt[N],cnt,val;
struct node
{
int l,r,v;
}t[72*N];
void partition(int v,int l,int r)
{
if(l==r){t[v].v=mex[l];return;}
t[v].v=inf;int mid=l+r>>1;
partition(A=++cnt,l ,mid);
partition(B=++cnt,mid+1,r );
}
void modify(int&v,int l,int r)
{
if(r<x||l>y)return;
t[++cnt]=t[v],v=cnt;
if(x<=l&&r<=y){t[v].v=min(t[v].v,val);return;}
int mid=l+r>>1;
modify(A,l,mid);modify(B,mid+1,r);
}
void maketree()
{
partition(rt[1]=cnt=1,1,n);
fo(i,1,n-1)
{
x=i+1;y=next[i]-1;val=a[i];
modify(rt[i+1]=rt[i],1,n);
}
}
int ans;
void query(int v,int l,int r)
{
ans=min(ans,t[v].v);
if(l==r)return;
int mid=l+r>>1;
if(x<=mid)
query(A,l ,mid);
else query(B,mid+1,r );
}
void work()
{
fo(i,1,m)
{
scanf("%d%d",&l,&r);if(tp)l^=ans,r^=ans;
x=r;ans=inf;query(rt[l],1,n);
printf("%d\n",ans);
}
}
int main()
{
freopen("mex.in","r",stdin);
freopen("mex.out","w",stdout);
scan();
maketree();
work();
}
算法Ⅲ:主席树
不难发现,算法Ⅱ即不直观又比较复杂,这里讲一种比较简单的做法。
对于每个右端点i,我们都可以建一棵线段树,记录1~i中每个数最后一次出现的位置。比如a={112,111,112,34,5135},当i=3时,线段树的第111个位置为2(a[2]=111),第112个位置为3(a[3]=112)。然后,这棵线段树是存储min值的(比如上例中表示区间[111..112]的线段树的节点储存的值为min(2,3)=2,而表示区间[109..112]的点存储的值则为min(0,2)=0)。这样存储原因之后再说。
对于每个询问[l,r],我们在第r棵线段树上查找。设当前节点为v,其左右儿子分别为A、B,线段树为t,若t[A]<l 则走左边;否则走右边。因为若t[A]<l,则说明左半区间中至少有一个数最后一次出现在l之前,亦即左半区间中至少有一个数在[l,r]区间中没有出现;而我们又要mex值尽量小。
时间复杂度:
O((n+m)∗log2ai)
O
(
(
n
+
m
)
∗
l
o
g
2
a
i
)
。期望得分:100。
Code
#include <cstdio>
#define A t[v].l
#define B t[v].r
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int N=2e5+1,Mx=1e9;
int i,n,m,tp,a,rt[N],cnt;
struct node
{
int v,l,r;
}t[N*32];
void modify(int&v,int l,int r)
{
t[++cnt]=t[v];v=cnt;
if(l==r){t[v].v=i;return;}
int mid=l+r>>1;
a<=mid?modify(A,l,mid):modify(B,mid+1,r);
t[v].v=min(t[A].v,t[B].v);
}
void maketree()
{
scanf("%d%d%d",&n,&m,&tp);
fo(i,1,n)
{
scanf("%d",&a);
modify(rt[i]=rt[i-1],0,Mx);
}
}
int l,r,x,ans;
void query(int v,int l,int r)
{
if(!A){ans=l;return;}
int mid=l+r>>1;
t[A].v<x?query(A,l,mid):query(B,mid+1,r);
}
void work()
{
fo(i,1,m)
{
scanf("%d%d",&l,&r);if(tp)l^=ans,r^=ans;
x=l;query(rt[r],0,Mx);printf("%d\n",ans);
}
}
int main()
{
freopen("mex.in","r",stdin);
freopen("mex.out","w",stdout);
maketree();
work();
}