CF1000F One Occurrence 题解
Chancylaser · · 题解
\mathcal{Preface}
我今天一整个晚上使用了主席树,想了很多种实现方式,但是怎么做都有点问题,最终从以前做过的 P1972 [SDOI2009] HH的项链 找到了思路,其实这两道题实现方式挺像的,可以说双倍经验。
\mathcal{Solution}
这是一篇主席树的题解。
题目要求我们输出一个区间
考虑如何暴力实现。
观察数据范围,可以发现数的范围不大,完全可以开下一个值域的数组。于是我们可以对于每个询问,暴力枚举每一个数,并统计出现次数。统计完成后再次枚举一遍,如果某个数出现的次数为
然而时间复杂度为
思考暴力的本质,即我们如果发现区间
一个显而易见的思路是,我们维护一个建立在数组下标上的主席树,每个节点维护此节点范围内所有数中最小的
如何解决这个问题?再次仔细思考,发现每次出现一个新的
然后查询的时候,返回此区间内
时间复杂度
将序列最大值看作与
- 注意,由于我们每次可能有两次修改操作,所以数组大小需要乘二。
\mathcal{Code}
如有细节问题不会实现,可以参考下面的代码,自认为写得比较好看。
#include<bits/stdc++.h>
#define PII pair<int,int>
#define F first
#define S second
using namespace std;
typedef long long LL;
const int N=5e5+5,M=41 ,INF=1e9,MX=5e5;
int n,Q;
int a[N];
struct qwq{
int ls,rs;
int mn,wh;
// mn 为此主席树节点包含所有下标最小的 lst_x,wh 为其对应的 x。
qwq(){
ls=rs=0;
mn=INF; wh=0;
// mn 初始需要设置为正无穷。
}
}t[N*M];
int rt[N],cnt;
qwq pushup(qwq c,qwq a,qwq b){
if(a.mn < b.mn){
c.mn=a.mn;
c.wh=a.wh;
}
else{
c.mn=b.mn;
c.wh=b.wh;
}
return c;
}
void Insert(int p,int &q,int l,int r,int x,int val){
q=++cnt;
t[q]=t[p];
if(l==r){
t[q].mn=val;
t[q].wh=a[x];
return;
}
int mid=(l+r)>>1;
if(x<=mid) Insert(t[p].ls,t[q].ls,l,mid,x,val);
else Insert(t[p].rs,t[q].rs,mid+1,r,x,val);
t[q]=pushup(t[q],t[t[q].ls],t[t[q].rs]);
}
qwq query(int p,int l,int r,int x,int y){
if(l>=x && r<=y) return t[p];
int mid=(l+r)>>1;
if(y<=mid) return query(t[p].ls,l,mid,x,y);
else if(x>mid) return query(t[p].rs,mid+1,r,x,y);
else return pushup(t[p],query(t[p].ls,l,mid,x,y),query(t[p].rs,mid+1,r,x,y));
}
int lst[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(lst[a[i]]){
Insert(rt[i-1],rt[i],1,MX,lst[a[i]],INF);
Insert(rt[i],rt[i],1,MX,i,lst[a[i]]);
}
else Insert(rt[i-1],rt[i],1,MX,i,0);
lst[a[i]]=i;
}
scanf("%d",&Q);
int l,r;
while(Q--){
scanf("%d%d",&l,&r);
qwq ans=query(rt[r],1,MX,l,r);
if(ans.mn<l) printf("%d\n",ans.wh);
else printf("0\n");
}
return 0;
}