题目:传送门
思路:莫队板子题。。
主席树变形。以往的主席树模板题都是建立可持续化权值线段树(求区间第k大,求区间小于k的数量等),而这道题需要建立可持续化的普通线段树(维护区间而非维护值域)。
对于a[i],我们判断其之前是否出现过,如果没有,则直接在i上加个1,否则为了保证每个数字只算一次,我们在当前版本的线段树对上一次a[i]出现的位置进行-1(先开辟一条-1的链,然后在当前版本下替换的以前版本的树链,保证之前版本的线段树不受影响)。这样就能保证对于重复出现的数字,在其对应的线段树版本中,我们取最靠右的(贪心思想)。对于每一个右端点都是一个不同版本的线段树([1,x]建树)。
那么当查询 l and r 时,只需在r端点对应版本的线段树查询区间[l,r]的和即可。
#include<cctype> #include<cstring> #include<cstdio> #include<functional> #include<algorithm> #include<cmath> #include<queue> #pragma GCC optimize(2) using namespace std; typedef long long LL; typedef pair<int,int> pii; typedef pair<double,double> pdd; const int N=2e5+5; const int inf=0x3f3f3f3f; const int mod=1e9+7; const double eps=1e-9; const long double pi=acos(-1.0L); #define ls (i<<1) #define rs (i<<1|1) #define fi first #define se second #define pb push_back #define mk make_pair #define mem(a,b) memset(a,b,sizeof(a)) LL read() { LL x=0,t=1; char ch; while(!isdigit(ch=getchar())) if(ch=='-') t=-1; while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); } return x*t; } struct node{int l,r,v;}c[N*40]; int a[N],t[N],n,m,cnt,len,root[N],last[N*10]; void update(int l,int r,int pre,int &now,int pos,int w) { c[++cnt]=c[pre],c[cnt].v+=w,now=cnt; if(l==r) return ; int mid=l+r>>1; if(pos<=mid) update(l,mid,c[pre].l,c[now].l,pos,w); else update(mid+1,r,c[pre].r,c[now].r,pos,w); } int query(int l,int r,int ll,int rr,int y) { if(ll<=l&&r<=rr) return c[y].v; int mid=l+r>>1,t1=0,t2=0; if(ll<=mid) t1=query(l,mid,ll,rr,c[y].l); if(rr>mid) t2=query(mid+1,r,ll,rr,c[y].r); return t1+t2; } int main() { n=read(); for(int i=1;i<=n;i++) { a[i]=read(); if(last[a[i]]) { int tmp; update(1,n,root[i-1],tmp,last[a[i]],-1); update(1,n,tmp,root[i],i,1); } else update(1,n,root[i-1],root[i],i,1); last[a[i]]=i; } m=read(); while(m--) { int x=read(),y=read(); printf("%d ",query(1,n,x,y,root[y])); } return 0; }