题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=5919
题意:
给定一个序列n,有m次查询,每次查询一个区间[l,r],求区间中每一种数在区间中第一次出现的位置的中位数,强制在线。
分析:
主席树
利用主席树求区间不同数的个数k
然后寻找区间第k/2大
有个很好的办法,倒着插
即主席树维护后缀[i,n],然后对于每次碰到一个数字,就把它以前的位置−1,新位置+1
因为是维护的后缀,直接找root[l]里的第k/2大出现的位置就行了,这样就可以1个log解决了
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
struct node {
int l,r,v;
} T[N*40];
int sz;
int update(int rt,int l,int r,int pos,int v) {
int k=sz++;
T[k].v=T[rt].v+v;
if(l==r)return k;
int mid=l+r>>1;
if(pos<=mid) {
T[k].r=T[rt].r;
T[k].l=update(T[rt].l,l,mid,pos,v);
} else {
T[k].l=T[rt].l;
T[k].r=update(T[rt].r,mid+1,r,pos,v);
}
return k;
}
int query(int rt,int l,int r,int a,int b) {
if(a<=l&&r<=b)return T[rt].v;
int mid=l+r>>1;
int ans=0;
if(a<=mid)ans+=query(T[rt].l,l,mid,a,b);
if(b>mid)ans+=query(T[rt].r,mid+1,r,a,b);
return ans;
}
int find_K(int rt,int l,int r,int K) {
if(l==r)return l;
int mid=l+r>>1;
if(T[T[rt].l].v>=K)return find_K(T[rt].l,l,mid,K);
else return find_K(T[rt].r,mid+1,r,K-T[T[rt].l].v);
}
int n,m,a[N],root[N],pre[N];
int main() {
//freopen("f.txt","r",stdin);
int T;
scanf("%d",&T);
for(int cas=1; cas<=T; cas++) {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)scanf("%d",&a[i]);
memset(pre,0,sizeof(pre));
root[n+1]=0;
sz=1;
for(int i=n; i; i--) {
if(!pre[a[i]])root[i]=update(root[i+1],1,n,i,1);
else {
int t=update(root[i+1],1,n,pre[a[i]],-1);
root[i]=update(t,1,n,i,1);
}
pre[a[i]]=i;
}
int ans=0;
printf("Case #%d:",cas);
while(m--) {
int l,r;
scanf("%d%d",&l,&r);
l=(l+ans)%n+1;
r=(r+ans)%n+1;
if(l>r)swap(l,r);
int k=query(root[l],1,n,l,r);
ans=find_K(root[l],1,n,(k+1)/2);
printf(" %d",ans);
}
printf("\n");
}
return 0;
}