3524: [Poi2014]Couriers
Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 710 Solved: 215
[ Submit][ Status][ Discuss]
Description
给一个长度为n的序列a。1≤a[i]≤n。
m组询问,每次询问一个区间[l,r],是否存在一个数在[l,r]中出现的次数大于(r-l+1)/2。如果存在,输出这个数,否则输出0。
Input
第一行两个数n,m。
第二行n个数,a[i]。
接下来m行,每行两个数l,r,表示询问[l,r]这个区间。
Output
m行,每行对应一个答案。
Sample Input
7 5
1 1 3 2 3 4 3
1 3
1 4
3 7
1 7
6 6
1 1 3 2 3 4 3
1 3
1 4
3 7
1 7
6 6
Sample Output
1
0
3
0
4
0
3
0
4
HINT
【数据范围】
n,m≤500000
首先这是个静态区间问题,由于区间是[L,R],我们知道如果L恒为1的话,那么这题可以用线段树或者树状数组轻松解决,这里当然不能按值建树,而应该是离散化后,按下标建树。
那么L不为1的情况,我们考虑是否可以利用前缀和的思想呢?[L,R]可以表示成[1,R]-[1,L-1],显然是可以的,因为线段树记录的下标为i的数出现的次数,这个是满足加减性质。
显然我们不能只维护一颗线段树了,我们要建n棵线段树,对于前i个数(1<=i<=n)建线段树。这样空间会达到O(n^2),我们想是否可以共用一些节点呢?于是就出现了函数式线段树(或者说主席树吧)。
他的原理是对于区间[1,i]的线段树和区间[1,i+1]的线段树,只有logn个节点不同,于是我们可以在[1,i]线段树的基础上插入a[i],沿a[i]插入的路径建立logn个节点,然后维护好左右儿子关系,沿左儿子下去的,右儿子是共用的,同理沿右儿子下去的,左儿子是共用的。具体实现请参考其他资料学习!
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define L l,mid,ls[x],ls[y]
#define R mid+1,r,rs[x],rs[y]
#define Maxn 10000010
using namespace std;
int ls[Maxn],rs[Maxn],s[Maxn];
int a[500010],sa[500010],root[500010];
int sz,tot;
void update(int l,int r,int x,int &y,int v){
y=++sz;
s[y]=s[x]+1;
if(l==r) return;
ls[y]=ls[x],rs[y]=rs[x];
int mid=l+r>>1;
if(v<=mid) update(L,v);
else update(R,v);
}
int query(int l,int r,int x,int &y,int k){
if(l==r) return l;
int mid=l+r>>1;
if(s[ls[y]]-s[ls[x]]>=k) return query(L,k);
else if(s[rs[y]]-s[rs[x]]>=k) return query(R,k);
return 0;
}
int main()
{
int n,m,l,r;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
sa[i]=a[i];
}
sort(sa+1,sa+n+1);
tot=unique(sa+1,sa+1+n)-sa-1;
for(int i=1;i<=n;i++){
int t=lower_bound(sa+1,sa+tot+1,a[i])-sa;
update(1,tot,root[i-1],root[i],t);
}
while(m--){
scanf("%d%d",&l,&r);
int ans=query(1,tot,root[l-1],root[r],(r-l+1)/2+1);
if(!ans) puts("0");
else printf("%d\n",sa[ans]);
}
return 0;
}