poj 3368 离散化+线段树+二分

Frequent values
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 13911 Accepted: 5098

Description

You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In addition to that, you are given several queries consisting of indices i and j (1 ≤ i ≤ j ≤ n). For each query, determine the most frequent value among the integers ai , ... , aj.

Input

The input consists of several test cases. Each test case starts with a line containing two integers n and q (1 ≤ n, q ≤ 100000). The next line contains n integers a1 , ... , an (-100000 ≤ ai ≤ 100000, for each i ∈ {1, ..., n}) separated by spaces. You can assume that for each i ∈ {1, ..., n-1}: ai ≤ ai+1. The following q lines contain one query each, consisting of two integers i and j (1 ≤ i ≤ j ≤ n), which indicate the boundary indices for the 
query.

The last test case is followed by a line containing a single 0.

Output

For each query, print one line with one integer: The number of occurrences of the most frequent value within the given range.

Sample Input

10 3
-1 -1 1 1 1 1 3 10 10 10
2 3
1 10
5 10
0

Sample Output

1
4
3

Source

给出一个单调非减序列,询问指定 区间出现最频繁的值的出现次数。

利用线段树做,每个节点保存出现的最大频数cnt,假如查询区间[a,b],对于区间为[l,r]的节点,如果[a,b]在其[l,r]左半部分(b<=m),则递归到左子树求解,若在又右半部分(a>m)则递归到右子树。

假如a<=m<b,则查询左子树[a,m]的最大值和右子树[m+1,b]的最大值 cnt = max(lchild.cnt, rchild.cnt)。由于是单调非减的,所以出现次数最多的数可能一部分在左半区间的末端,另一部分在右半区间的起始端,所以要判断if(data[m]==data[m+1])  /*数据都存在data数组中*/,不等于(也就是小于)直接返回cnt。如果等于二分查找data[m]在左半区间出现的次数,和右半区间的次数。更新cnt最大值

这个方法交c++ 1079ms。。。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

#define maxn 100005

struct node
{
    int l, r;
    int cnt;

    node() {}
    node(int ll, int rr){ l = ll; r = rr;}
};

int data[maxn];
node tr[4*maxn];

void build(int rt, int l, int r)
{
    tr[rt] = node(l, r);
    if(l == r){
        tr[rt].cnt = 1;
        return;
    }

    int m = l+(r-l)/2;
    int lc = 2*rt, rc = 2*rt+1;
    build(lc, l, m);
    build(rc, m+1, r);

    tr[rt].cnt = max(tr[lc].cnt, tr[rc].cnt);

    if(data[m] == data[m+1]){
        int v = data[m];
        int cnt = m -(lower_bound(data+l, data+m+1, v)-data)+1;
        cnt += (upper_bound(data+m+1, data+r+1, v)-data)-(m+1);
        tr[rt].cnt = max(tr[rt].cnt, cnt);
    }
}


int query(int rt, int ll, int rr)
{
    int l = tr[rt].l, r = tr[rt].r;
    if(l == ll && r == rr)
        return tr[rt].cnt;

    int m = l+(r-l)/2;
    int lc = 2*rt, rc = 2*rt+1;
    if(rr <= m)
        return query(lc, ll, rr);
    if(ll > m)
        return query(rc, ll, rr);

    int ret = max(query(lc,ll, m), query(rc, m+1, rr));
    if(data[m] == data[m+1]){
        int v = data[m];
        int cnt = m -(lower_bound(data+ll, data+m+1, v)-data)+1;
        cnt += (upper_bound(data+m+1, data+rr+1, v)-data)-(m+1);
        ret = max(cnt, ret);
    }

    return ret;
}

int main()
{
    int n, q;
    while(~scanf("%d", &n) && n){
        scanf("%d", &q);
        for(int i = 1; i <= n; i++)
            scanf("%d", data+i);

        build(1, 1, n);
        int a,b;
        for(int i = 0; i < q; i++){
            scanf("%d%d", &a, &b);
            printf("%d\n", query(1, a, b));
        }
    }
    return 0;
}

还有一种做法是将数据进行离散化处理,将相同的数合并,并记录每个数出现的次数,再建立线段树记录出现的最大次数。每次询问[a,b],利用前缀和cnt[n]预处理计算前n个数总共出现多次,每次先二分查找a和b对应的下标位置l和r,对于完全包含的a,b里面的数[l+1,r-1]用线段树查询最大值,再跟cnt[l]-a+1, b-cnt[r-1]进行比较得到最大值

用c++跑625ms....

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

#define maxn 100005

struct node
{
    int l, r;
    node(){}
    node(int ll , int rr) {l = ll; r = rr;}
};

node tree[4*maxn];
int maxi[4*maxn];
int data[maxn];
int cnt[maxn];

void build(int rt, int l, int r)
{
    tree[rt] = node(l, r);

    if(l == r){
       maxi[rt] = cnt[l]-cnt[l-1];
       return;
    }

    int m = l+(r-l)/2;
    int lc = 2*rt, rc = 2*rt+1;
    build(lc, l, m);
    build(rc, m+1, r);
    maxi[rt] = max(maxi[lc], maxi[rc]);
}

int query(int rt, int ll, int rr)
{
    if(ll > rr) return 0;
    int l = tree[rt].l, r = tree[rt].r;

    if(l == ll && r == rr)
        return maxi[rt];

    int m = l+(r-l)/2;
    if(rr <= m)
        return query(2*rt, ll, rr);
    else if(ll > m)
        return query(2*rt+1, ll, rr);
    else
        return max(query(2*rt, ll, m), query(2*rt+1, m+1, rr));
}

int main()
{
    int n, q;
    while(~scanf("%d",&n) && n){
        scanf("%d", &q);
        for(int i = 1; i <= n; i++)
            scanf("%d", data+i);
        data[0] = data[1];

        int cur = 1;
        int tot = 0;
        cnt[0] = 0;
        for(int i = 1; i <= n; i++){
            if(data[i] == data[i-1])
                tot++;
            else{
                data[cur] = data[i-1];
                cnt[cur] = cnt[cur-1]+tot;
                tot = 1;
                cur++;
            }
        }
        cnt[cur] = cnt[cur-1]+tot;
        build(1, 1, cur);

        int a, b;
        for(int i = 0; i < q; i++){
            scanf("%d%d", &a, &b);
            int l = lower_bound(cnt+1, cnt+cur+1, a)-cnt;
            int r = lower_bound(cnt+1, cnt+cur+1, b)-cnt;

            if(l == r)
                printf("%d\n",b-a+1);
            else{
                int ans;
                ans = query(1, l+1, r-1);
                ans = max(cnt[l]-a+1, ans);
                ans = max(ans, b-cnt[r-1]);
                printf("%d\n", ans);
            }
        }
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值