bzoj3524 区间问题

3524: [Poi2014]Couriers

Time Limit: 20 Sec   Memory Limit: 128 MB
Submit: 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



Sample Output

1

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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值