Codeforces 1000F 线段树+离线处理

http://codeforces.com/problemset/problem/1000/F

You are given an array aa consisting of nn integers, and qq queries to it. ii -th query is denoted by two integers lili and riri . For each query, you have to find any integer that occurs exactly once in the subarray of aa from index lili to index riri (a subarray is a contiguous subsegment of an array). For example, if a=[1,1,2,3,2,4] , then for query (li=2,ri=6) the subarray we are interested in is [1,2,3,2,4] , and possible answers are 1 , 3 and 4 ; for query (li=1,ri=2) the subarray we are interested in is [1,1], and there is no such element that occurs exactly once.

Can you answer all of the queries?

Input

The first line contains one integer n (1≤n≤5⋅10^5 ).

The second line contains nn integers a1,a2,…,an (1≤ai≤5⋅10^5).

The third line contains one integer q (1≤q≤5⋅10^5).

Then qq lines follow, ii -th line containing two integers lili and riri representing ii -th query (1≤li≤ri≤n).

Output

Answer the queries as follows:

If there is no integer such that it occurs in the subarray from index lili to index riri exactly once, print 00 . Otherwise print any such integer.

Example

Input

6
1 1 2 3 2 4
2
2 6
1 2

Output

4
0

题目大意:给出n个数字,要查询m次,对每次查询若区间[l,r]内存在一个数在该区间内只出现了一次,则输出该数(有多个则输出任意一个)否则输出0。

思路:这道题好难啊,一直没有什么很好的思路,看了别人的代码也是一知半解的,写了好久终于写出来了orz。基本思路就是用pos[i]存储i上一次出现的位置,那么对于区间[l,r],若其中的某个元素a[i]满足pos[a[i]]<l,那么这个a[i]必定是满足题意的,若没有元素满足这个条件,那么就记为0。因此,我们的想法就是维护所有满足pos[a[i]]<l的pos[a[i]]的最小值。此外我们很容易发现,当一个元素在该序列中已经出现过的时候,对后续的维护有影响的,比如:a[1]=1,a[2]=1,那么我们容易得到:[1,1]的答案是1;[2,2]的答案是1;但是[1,2]的答案是0,因为在区间[1,2]内,pos[1]=1,即元素1上一次出现的位置是1,而该区间左端点也是1,显然1<1不成立。由此我们可以看出,pos[i]记录的是上一次i出现的位置,对于上上次或者更靠前出现的位置对我们没有用处,但是不处理的话,会影响后续的维护,前面已经说过了,维护的是满足题意的元素的位置的最小值,按照这个逻辑,我们应该取min(0,1) (分别对应上上次出现和上一次出现的位置),又0<1,这就推出了矛盾。因此我们必须对以前已经出现过的元素进行处理,怎么处理呢,我们把它的距离修改成INF即可(因为取的是最小值嘛 这个这么大不会选它的 这个操作是对线段树的叶子节点进行的 因为维护操作是线段树在做),只要这个元素以前出现过现在又出现了,就要进行处理(很容易理解 对于区间[l,r] 若其左区间位置i元素为k 右区间位置j元素也为k 因为这个操作使线段树[i,i]叶子节点的距离变成INF 那么对该区间进行pushup操作时 必定不会选k)。也正是这个原因,导致我们必须从左到右处理序列,因此要进行离线操作,即把查询按照右区间排序。思路大概就是这样,具体的实现就看代码吧。我这里线段树维护是一个pair<int,int> p,p.second(后者)代表元素,p.first(前者)代表该元素在序列中上一次出现的位置,两个pair类型的比较会先比较前者即先比较位置,因此维护的依然是位置的最小值。(确实有点难理解 要多思考多想想 代码的注释也比较详细 毕竟好几个小时才做出来 还是认真对待吧)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;

const int maxn=500005;

struct qry//查询
{
	int l,r,id;//查询区间的左右端点以及编号
	bool operator < (const qry &a)const//右端点排序
	{
		if(r==a.r)
			return l<a.l;
		return r<a.r;
	}
};

struct node//线段树节点
{
	int l,r;//左右端点
	pair<int,int> p;//位置 数字
};

qry ask[maxn];//查询
node tree[maxn<<2];//线段树
int a[maxn];//数字序列
int pos[maxn];//pos[i]记录i上一次出现的位置
int re[maxn];//记录最终结果

void build(int i,int l,int r)//建树
{
	tree[i].l=l;
	tree[i].r=r;
	if(l==r)
		return ;
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
}

void update(int i,int t,int p,int n)//i:节点编号 t:位置 p:上一次出现的位置 n:数字
{
	if(tree[i].l==tree[i].r&&tree[i].l==t)//单点修改
	{
		tree[i].p=pair<int,int>(p,n);
		return ;
	}
	int mid=(tree[i].l+tree[i].r)>>1;
	if(t<=mid)
		update(i<<1,t,p,n);
	else
		update(i<<1|1,t,p,n);
	tree[i].p=min(tree[i<<1].p,tree[i<<1|1].p);//维护位置的最小值
}

pair<int,int> query(int i,int l,int r)//区间查询 返回的是pair类型
{
	if(tree[i].l==l&&tree[i].r==r)
		return tree[i].p;
	int mid=(tree[i].l+tree[i].r)>>1;
	if(r<=mid)
		return query(i<<1,l,r);
	else if(l>mid)
		return query(i<<1|1,l,r);
	else
		return min(query(i<<1,l,mid),query(i<<1|1,mid+1,r));
}

int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int m;
	scanf("%d",&m);
	for(int i=0;i<m;i++)
	{
		scanf("%d %d",&ask[i].l,&ask[i].r);
		ask[i].id=i;
	}
	sort(ask,ask+m);//排序 离线操作
	build(1,1,n);//建树
	int s=1;//当前要处理第s个元素
	for(int i=0;i<m;i++)//m次查询
	{
		while(s<=ask[i].r)//处理到当前查询的右端点为止
		{
			if(pos[a[s]])//a[s]以前出现过 pos[a[s]]是a[s]上次出现的位置
				update(1,pos[a[s]],INF,a[s]);//把上次出现的位置 置为INF
							//(注意:update修改的是线段树的东西 并没有修改pos数组)
			update(1,s,pos[a[s]],a[s]);//更新操作 s:a[s]当前的位置 pos[a[s]]:a[s]上次出现的位置
			pos[a[s]]=s;//更新pos数组
			s++;
		}
		pair<int,int> tp=query(1,ask[i].l,ask[i].r);
		if(tp.first<ask[i].l)//位置最小值 小于该查询区间的左端点 满足题意
			re[ask[i].id]=tp.second;
	}
	for(int i=0;i<m;i++)
		printf("%d\n",re[i]);
	return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值