【题目描述】
HDU - 5919 Sequence II
【题目分析】
题目给定一个数组,每次查询一个区间,找出区间内不同数字的个数x,然后输出按出现顺序第x/2向上取整个数字的位置。
- 按照要求,我们首先需要能够找出给定区间不同的数字个数。
首先,我们分析一个简单一些的问题:对于右端点固定的区间,如何计算不同左区间内不同数字的个数。
我们不妨用一个数组记录 c n t cnt cnt哪些位置出现了一个不同的数字,用 s u m sum sum数组进行维护 c n t [ 1.. l ] cnt[1..l] cnt[1..l]的和(可以用线段树或者树状数组),那么对于区间 [ l , r ] [l,r] [l,r]内不同数字的个数就是 s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]−sum[l−1]。
在从前往后进行添加的过程中,如果该数字在前面已经出现,就将前面的标记消除,在后面的位置进行标记,也就是说尽可能将标记后放。例如对于数组 1 , 2 , 2 , 3 , 5 , 1 1,2,2,3,5,1 1,2,2,3,5,1维护以后的 c n t cnt cnt数组就是 0 , 0 , 1 , 1 , 1 , 1 0,0,1,1,1,1 0,0,1,1,1,1,这样做的原因是我们假设的是右端点固定,对于重复的元素,在后面如果出现过前面就没有必要标记。
如果询问是离线的,我们大可以先将询问保存下来,然后从前往后加入数据的过程中不断将对应的询问答案保存(对应是指右端点相同),最后输出就可以了。
可是这个问题是强制在线的,所以我们必须使用主席树进行可持久化。可是这种可持久化和以前的主席树运用不同,因为在添加的过程中会将前面的标记消除,所以不同根节点的主席树不在拥有可以互相加减的能力(加减的结果不再有意义)。然而在我们这个问题里面我们是不需要进行加减的。 - 不同于求区间第K大的时候我们的主席树维护的是值区间,即值在区间内的个数,这里根节点的 1.. n 1..n 1..n指的是数据范围 a i ai ai的最大值。在这里我们的根节点的 1.. n 1..n 1..n的 n n n指的是数据的个数,就是题目中的 n n n,标记的是该位置上出现了一个之前没有出现过的数字。 因此对于每次询问,我们访问的版本里保存的就是实际的数组,直接计数就可以。而区间第K大就需要减去之前的版本才是该区间内的数的个数。
- 题目要求的是数据第一次出现的位置,可是按照上面的想法进行建树的话我们保存的是当前区间
[
1
,
i
]
[1,i]
[1,i]数据最后一次出现的位置。很自然,我们应该进行逆序建树,这样的话我们保存的就是
[
i
,
n
]
[i,n]
[i,n]区间内数据第一次出现的位置,对于需要查询的区间
[
l
,
r
]
[l,r]
[l,r],我们访问第
l
l
l个版本的主席树,就满足题目要求啦。
求出数字的个数后我们再除以2向上取整就是题目要求的k,然后在找出对应的位置,这里类似区间第K大
【参考文献】
大佬博客1
大佬博客2
【AC代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<climits>
#include<string>
#include<cmath>
#include<cstdlib>
using namespace std;
const int MAXN=200005;
int a[MAXN];
int n,m;
struct node
{
int ls,rs,cnt;
}tree[MAXN*40];
int root[MAXN*40];
int b[MAXN];
int tot;
void Insert(int &now,int pre,int x,int l,int r,int add)
{
int tmp=now; now=++tot;
tree[now]=tmp?tree[tmp]:tree[pre];
tree[now].cnt+=add;
if(l==r) return;
int mid=(l+r)>>1;
if(x<=mid) Insert(tree[now].ls,tree[pre].ls,x,l,mid,add);
else Insert(tree[now].rs,tree[pre].rs,x,mid+1,r,add);
}
int GetSum(int k,int l,int r,int L,int R)
{
if(l>=L && r<=R) return tree[k].cnt;
int ret=0; int mid=(l+r)>>1;
if(L<=mid) ret+=GetSum(tree[k].ls,l,mid,L,R);
if(R>mid) ret+=GetSum(tree[k].rs,mid+1,r,L,R);
return ret;
}
int query(int k,int l,int r,int x)
{
if(l==r) return l;
int mid=(l+r)>>1;
int tmp=tree[tree[k].ls].cnt;
if(x<=tmp) query(tree[k].ls,l,mid,x);
else query(tree[k].rs,mid+1,r,x-tmp);
}
int main()
{
int T,ans,l,r,tmp,sum;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++)
{
scanf("%d%d",&n,&m);
memset(tree,0,sizeof(tree));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(root,0,sizeof(root));
tot=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=n;i>0;i--)
{
if(b[a[i]]) Insert(root[i],root[i+1],b[a[i]],1,n,-1);
Insert(root[i],root[i+1],i,1,n,1);
b[a[i]]=i;
}
ans=0;
printf("Case #%d:",Case);
//测试
//printf("\n");
for(int i=0;i<m;i++)
{
scanf("%d%d",&l,&r);
l=(l+ans)%n+1; r=(r+ans)%n+1;
if(l>r) tmp=l,l=r,r=tmp;
//printf("test: l=%d r=%d\n",l,r);
sum=GetSum(root[l],1,n,l,r);
//printf("test: sum=%d\n",sum);
sum=(sum+1)/2;
//printf("test: sum=%d\n",sum);
ans=query(root[l],1,n,sum);
printf(" %d",ans);
}
printf("\n");
}
return 0;
}