Sequence II(HDU - 5919)

本文介绍了一种处理区间中位数查询的高效算法,针对一个整数序列,通过主席树记录不同数值首次出现的位置,实现对指定子区间内所有不同整数首次出现位置中位数的快速查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

Mr. Frog has an integer sequence of length n, which can be denoted as a1,a2,⋯,an.There are m queries.
In the i-th query, you are given two integers lili and riri. Consider the subsequence ali,ali+1,ali+2,⋯,ari.
We can denote the positions(the positions according to the original sequence) where an integer appears first in this subsequence as p(i)1,p(i)2,⋯,p(i)kip1(i),p2(i),⋯,pki(i) (in ascending order, i.e.,p(i)1<p(i)2<⋯<p(i)ki).
Note that ki is the number of different integers in this subsequence. You should output p(i)⌈ki/2⌉for the i-th query.

Input
In the first line of input, there is an integer T (T≤2) denoting the number of test cases.

Each test case starts with two integers n (n≤ 2×10^5) and m (m≤ 2×10 ^5). There are n integers in the next line, which indicate the integers in the sequence(i.e.,a1,a2⋯,an,
0≤ai≤2×10 ^5).

There are two integers li and ri in the following m lines.

However, Mr. Frog thought that this problem was too young too simple so he became angry. He modified each query to l‘i,r‘i(1≤l‘i≤n,1≤r‘i≤n). As a result, the problem became more exciting.

We can denote the answers as ans1,ans2,⋯,ansm. Note that for each test case ans0=0.

You can get the correct input li,ri from what you read (we denote them as l‘i,r‘i)by the following formula:
在这里插入图片描述

Output
You should output one single line for each test case.
For each test case, output one line “Case #x: p1,p2,⋯,pm”, where x is the case number (starting from 1) and p1,p2,⋯,pm is the answer.

Sample Input
2
5 2
3 3 1 5 4
2 2
4 4
5 2
2 5 2 1 2
2 3
2 4

Sample Output
Case #1: 3 3
Case #2: 3 1

Hint
在这里插入图片描述
直戳明了的说出题目要求:
给一个长度为N的数列A,有m个询问,每次问数列[l,r]区间中所有数的第一次出现的位置的中位
数是多少~

这里有两个问题我们需要解决,一个是求区间内不同数的个数,第二是求第K大

第一个方法有两种
(主席树记录的是区间不同数的数量)
第一种

记录一下每个数上一次出现的位置
只要重复遇到,那么在这条链上的主席树上
将之间区间[1,last[i]]里面数量减1
然后在整体区间数量加1即可
这样就满足了要求
接查询区间和即可

第二种
将序列倒过来插入
每次记录这个数上一次出现的位置(倒过来的!!!)
然后同理~

不同点:
第一种最后记录的每个数字都是最后一次出现的位置
第二种最后记录的每个数字都是第一次出现的位置

这道题目既然要求所有的第一次出现的数的位置的中位数
显然要维护位置信息,所以考虑第二种方法,
第一种维护的是上一次出现位置的信息
查询一下区间内有多少个数,然后求区间第K大的位置即可

代码如下:(有注释哦~(。・∀・)ノ)

#include<bits/stdc++.h>
#define mx 200010
using namespace std;
struct node{ 
   int ls,rs,sum;//分别为左儿子,右儿子,区间不同数量
}s[mx*40];

int a[mx],rt[mx],last[mx],ans[mx];//输入,每个数据代表的编号,数字上一次出现的位置,结果
int tot,tt,n,m;//总编号,输入的T,n长度,m次查询
void update(int &now,int pre,int l,int r,int k,int w)
{//现在的编号,上一次编号,长,度,现在的顺序,权值
	now=++tot;
	s[now]=s[pre];
	s[now].sum=s[pre].sum+w;//变化区间数量
	if(l==r)return;
	int mid=(l+r)>>1;
	if(k<=mid) update(s[now].ls,s[pre].ls,l,mid,k,w);
	else update(s[now].rs,s[pre].rs,mid+1,r,k,w);
}

int query(int k,int l,int r,int L,int R)//查询嘛,和线段树一样
{
	if(l>=L&&r<=R) return s[k].sum;
	int mid=(l+r)>>1,ret=0;
	if(L<=mid) ret+=query(s[k].ls,l,mid,L,R); 
	if(R>mid) ret+=query(s[k].rs,mid+1,r,L,R);
	return ret;
}

int Kth(int k,int l,int r,int p)//主席树查询第K大嘛
{
	if(l==r) return l;
	int mid=(l+r)>>1,cnt=s[s[k].ls].sum;//中间值,左儿子数量
	if(p<=cnt) return Kth(s[k].ls,l,mid,p);
	else return Kth(s[k].rs,mid+1,r,p-cnt);
}


void prework()
{
	memset(last,0,sizeof(last));
	memset(rt,0,sizeof(rt));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	tot=0;
	for(int i=n;i>=1;i--)
	{
	  if(last[a[i]]==0)
	  update(rt[i],rt[i+1],1,n,i,1);
	  //表明a[i]是第一次出现,将区间[1,n]内满足在内的数量加1
	  else
	  {
	  	int temp;
	  	update(temp,rt[i+1],1,n,last[a[i]],-1);
	        /*这样做的原理是,在1-n的区间内,每种数只可能在某一个位置出现一次,
	        只是随着时间的推移,这个数出现的位置会发生改变,那么我就可以查询在某一时间点,
	        其中一种数在哪个位置出现过。*/
	  	update(rt[i],temp,1,n,i,1);
	  	//删除前面一个区间的,可以把整体区间+1了~
	  }
	  last[a[i]]=i;
    }
}


void mainwork()
{
	int l,r,k;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&l,&r);
		l=(l+ans[i-1])%n+1;//得到l
		r=(r+ans[i-1])%n+1;//得到r
		if(l>r)//因为一个是min,一个是max
		swap(l,r);
		k=query(rt[l],1,n,l,r);//首先查询得到多少个不同数量
		ans[i]=Kth(rt[l],1,n,(k+1)/2);//然后查询即可
	}
}

void print()
{
	printf("Case #%d:",tt);
	for(int i=1;i<=m;i++)
	printf(" %d",ans[i]);
	printf("\n");
}
int main()
{
	int t;
	scanf("%d",&t);
	for(tt=1;tt<=t;tt++)
	{
		prework();
		mainwork();
		print();
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值