HDU - 5919 Sequence II——主席树+区间种类++逆序建树

【题目描述】
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[l1]
    在从前往后进行添加的过程中,如果该数字在前面已经出现,就将前面的标记消除,在后面的位置进行标记,也就是说尽可能将标记后放。例如对于数组 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值