主元素 线段树

主元素

题目描述

给出一个数组A[1…n],再给出M个询问,第i个询问的格式是:Li、Ri,表示的意义是:A[Li]至A[Ri]这个区间中,是否存在“主元素”,如果存在则输出“yes”和主元素,否则输出“no”。
下面解释“主元素”:在一个包含X个元素的区间里面,如果有个元素出现的次数严格大于X/2,那么该元素就是区间的主元素。

输入格式

第一行,n和C。3 <= n <= 300000,1 <= C <= 10000。

第二行,n个正整数,第i个正整数表示A[i]。 其中1<=A[i]<=C。

第三行,一个整数M。1 <= M <= 10000。

接下来有M行,第i行给出:Li和Ri。1 <= Li <= Ri <= n。

输出格式

共M行,每行对应一个询问。

输入样例

10 3
1 2 1 2 1 2 3 2 3 3
8
1 2
1 3
1 4
1 5
2 5
2 6
6 9
7 10

输出样例

no
yes 1
no
yes 1
no
yes 2
no
yes 3


题目题解分界线



题解

本题涉及到了区间问题,首先考虑使用线段树这个数据结构

但是本题并没有涉及区间和、区间最大等经典线段树的问题,而是涉及元素的数量问题

那我们可不可以改造线段树来适应这一道题呢?当然可以

首先来思考一下主元素的特点:

1、在一个包含X个元素的区间里面,有一个元素出现的次数严格大于X/2

2、这个区间里可能没有主元素

再仔细分析,可以发现:

设有两个区间[l1,r1]和[l2,r2],(l1<=r1<=l2<=r2),那么这两个区间的主元素有以下关系:

1、若[l1,r1]和[l2,r2]都没有主元素,那么区间[l1,r2]也没有主元素,证明:

	∵[l1,r1]和[l2,r2]都没有主元素
	∴设出现次数最多的数x分别在两个区间内出现了(r1-l1)/2次和(r2-l2)/2次
	∴x在[l1,r2]内出现了(r2-l2+r1-l1)/2次,即(r2-l1)/2次
	∵(r2-l1)/2=(r2-l1)/2
    ∴若[l1,r1]和[l2,r2]都没有主元素,那么区间[l1,r2]也没有主元素

2、若[l1,r1]和[l2,r2]都有相同的主元素x,那么区间[l1,r2]的主元素为x,证明:

	∵[l1,r1]和[l2,r2]都有主元素x
	∴x分别在两个区间内最少出现了(r1-l1)/2+1次和(r2-l2)/2+1次
	∴x在[l1,r2]内出现了(r2-l2+r1-l1)/2+2次,即(r2-l1)/2+2次
	∵(r2-l1)/2+2>(r2-l1)/2
    ∴若[l1,r1]和[l2,r2]有相同的主元素x,那么区间[l1,r2]的主元素为x

3、若[l1,r1]和[l2,r2]都有主元素且分别为x和y(x≠y),那么区间[l1,r2]有可能没有主元素,有可能主元素为x,有可能主元素为y

此时我们就归纳出了维护线段树的做法,但是对于第三部的具体实现,仍然还没有一个确切的想法,

我们不妨尝试枚举区间[l1,r2],寻找x的个数和y的个数,通过比较确定主元素

这时我们又发现,如果直接暴枚,但询问次数多且r2-l1比较大时,那是肯定要TLE(Time Limit Exceed 超时)的,降低暴枚的时间复![]杂度,我们很容易想到,用二分才能将O(n)降到O(log2n)。

问题就在于:怎么二分

有一种巧妙的做法可以帮助我们:

首先开一个pos数组,我们用pos数组储存每一种元素序列中的所有位置

再开一个head数组,记录每一种元素在pos中开始的位置

为了方便,开sum数组,记录每一种元素的数量

在这里插入图片描述

然后我们就能轻易求出tail的值,在head和tail的范围内二分枚举在[l1,r2]内

最左边x出现的位置在pos中的位置pos1

最右边x的位置在pos中的位置pos2

x的数量为num=pos2-pos1

这么一来,这道题就完成了

#include<iostream>

using namespace std;
int n,c,m,a[300005],b[10005],head[10005],pos[300005],x,y;
struct node{
	int mainnum;
	int sum;
}tree[1200020],nul/*表示空*/,ans;

int searchmain(int le,int ri,int x)//原理:二分查找 
{
	if(x==0)
		return 0;
	/*确定元素x在位置查询表中 最开始出现的位置 与 最后出现的位置*/
	int start=head[x]-b[x]+1; 
	int end=head[x];
	/*----------------------------------------------------------*/ 
	int middle=0,low=0,high=0;
	if(pos[end]<le||pos[start]>ri)//不在区间内的情况 
		return 0;
	if(pos[start]>=le&&pos[end]<=ri)//被区间包含的情况 
		return end-start+1;
	/*二分查找左端点*/
	while(start<=end)
	{
		middle=(start+end)/2;
		if(pos[middle]<le)
			start=middle+1;
		else
		{
			end=middle-1;
			low=middle;
		}
	}
	start=head[x]-b[x]+1,end=head[x];
	/*二分查找右端点*/
	while(start<=end)
	{
		middle=(start+end)/2;
		if(pos[middle]<=ri)
		{
			start=middle+1;
			high=middle;
		}
		else
			end=middle-1;
	}
	return high-low+1;
}

void Toper(node &rt,int lmain,int rmain,int left,int right)//维护操作
{
	if(lmain==rmain)//如果两边的主元素相同,则两边的主元素为本区间的主元素 
	{
		rt.mainnum=lmain;
		rt.sum=searchmain(left,right,lmain);
	}
	else
	{
		int temp1=searchmain(left,right,lmain);//找左树的主元素 
		int temp2=searchmain(left,right,rmain);//找右树的主元素 
		/*--------确定主元素--------*/ 
		if(temp1>(right-left+1)/2)
		{
			rt.mainnum=lmain;
			rt.sum=temp1;
		}
		if(temp2>(right-left+1)/2)
		{
			rt.mainnum=rmain;
			rt.sum=temp2;
		}
		/*--------------------------*/
	}
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		tree[k].mainnum=a[l];
		tree[k].sum=1;
		return;
	}
	int mid=(l+r)/2;
	build(k*2,l,mid);
	build(k*2+1,mid+1,r);
	Toper(tree[k],tree[k*2].mainnum,tree[k*2+1].mainnum,l,r);
}

node qaf(int k,int l,int r,int x,int y)
{
	if(l>y||r<x) return nul;//如果目前区间不在需求区间内,返回空值 
	/*-------如果在区间内,直接返回主元素-------*/ 
	if(l>=x&&r<=y)
		return tree[k];
	/*-----------------------------------------*/
	int mid=(l+r)/2;
	node lc,rc,answ;
	lc.mainnum=lc.sum=rc.mainnum=rc.sum=answ.mainnum=answ.sum=0;
	lc = qaf(k*2,l,mid,x,y);//在左子树找主元素 
	rc = qaf(k*2+1,mid+1,r,x,y);//在右子树找主元素 
	/*--有其中一个子树不在需求区间时,另一个则可作为需求区间的主元素--*/
	if(lc.mainnum==-1) 
		return rc;
	if(rc.mainnum==-1)
		return lc;
	if(lc.mainnum==rc.mainnum)
		return lc;
	/*--------------------------------------------------------------*/ 
	/*----------确定需求区间的主元素----------*/
	Toper(answ,lc.mainnum,rc.mainnum,x,y);
	/*----------------------------------------*/
	return answ;
}

int main()
{
	nul.mainnum=-1;
	nul.sum=-1;
	cin>>n>>c;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		b[a[i]]++;
	}
	//-------位置查询表------- 
	for(int i=2;i<=c;i++)
		head[i]=head[i-1]+b[i-1];
	for(int i=1;i<=n;i++)
	{
		head[a[i]]++;
		pos[head[a[i]]]=i;
	}
	//------------------------- 
	build(1,1,n); 
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y;
		ans=qaf(1,1,n,x,y);
		if(ans.mainnum)
			cout<<"yes "<<ans.mainnum<<endl;
		else
			cout<<"no"<<endl; 
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值