poj Frequent values 3368

Frequent values
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 17832 Accepted: 6427

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

这个题我是被自己感动到了,没想到自己做的第二个RMQ的题可以一次就过,编译也是一次过,太感动了!

哈哈!

题意是:给出一个非降序的整数数组,你的任务是对于一系列询问,回答区间内出现最多的值的次数

说一下如何做

正如《算法竞赛入门经典(训练指南)》198页讲的,

这里我们因为是按非降序排列的,那也就是说相同的数字一定是在一起的

这里我们就用相同的数字为一段来描述

那么说例题中我们就有2 4 1 3分别代表四段

我们用a[]数组来存给出的值,

下面说说我们用到的数组的意义

value[]: 代表第i段的具体数值

coun[]: 代表第i段出现的次数

lef[] : 代表第i段的左端点的位置

righ[]: : 代表第i段的右端点的位置


num[] 数组稍有不同 他指的是第p个位置上为第几段

这样我们只要找num[L]+1, num[R]-1段中的最大值与righ[L]-L+1, R-lef[R]+1,三个中的最大值就可以了,

当然特殊情况也要有考虑

大致可分为三种:


代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;

const int inf = INT_MAX;
int value[300005], coun[300005];
int num[300005], lef[300005], righ[300005];
int a[300005], b[300005][20];
int top;

void inti()
{
    a[0] = inf;
    top = 0;
}

void RMQ()
{
    int i, j;
    for ( i = 1;i <= top; i++ )
        b[i][0] = coun[i];
    for ( j = 1;(1<<j) <= top; j++ )
    {
        for ( i = 1;i+(1<<(j-1)) <= top; i++ )
            b[i][j] = max(b[i][j-1], b[i+(1<<(j-1))][j-1]);
    }
}

int RMQ_check(int y, int x)
{
    int nb = 0;
    while ( (1<<nb) <= y-x+1 )
        nb++;
    return max(b[y-(1<<(nb-1))+1][nb-1], b[x][nb-1]);
}

int main()
{
    int i, n, m;
    int L, R;
    while ( ~scanf ( "%d", &n )&&n != 0 )
    {
        scanf ( "%d", &m );
        inti();
        for ( i = 1;i <= n; i++ )
        {
            scanf ( "%d", &a[i] );
            if ( a[i] != a[i-1] )
            {
                righ[top] = i-1;
                top++;
                lef[top] = i;
                value[top] = a[i];
                coun[top] = 0;
            }
            num[i] = top;
            coun[top]++;
        }
        righ[top] = n;
        RMQ();
        for ( i = 0;i < m; i++ )
        {
            scanf ( "%d %d", &L, &R );
            if ( num[L] == num[R] )
                printf ( "%d\n", R-L+1 );
            else if ( num[R]-num[L] == 1 )
                printf ( "%d\n", max(righ[num[L]]-L+1, R-lef[num[R]]+1) );
            else
            {
                int ma1 = righ[num[L]]-L+1;
                int ma2 =  R-lef[num[R]]+1;
                int ma3 = RMQ_check(num[R]-1, num[L]+1);
                printf ( "%d\n", max(ma1, max(ma2, ma3)));
            }
        }
    }
    return 0;
}

代码菜鸟,如有错误,请多包涵!!!

如有帮助记得支持我一下,谢谢!!!

<think>嗯,用户想要POJ 3368的代码实现,这个问题是关于频繁出现的数的查询。首先,我得回忆一下这个题目的具体要求。题目大意是给定一个非降序排列的数组,然后进行多次查询,每次查询一个区间内出现次数最多的数的次数。对吧?比如数组是-1 -1 1 1 1 1 3 10 10 10,查询区间[2,3],也就是第二个到第三个元素,这里都是1,所以返回2。而查询区间[1,10]的话,最多的是1出现4次,所以返回4。 那解决这个问题的常见方法是什么呢?可能涉及到分段处理或者预处理。因为数组是非降序的,相同的元素是连续的,所以可以考虑把相同的元素分块,记录每个块的起始位置、结束位置和出现次数。比如,把数组分成多个块,每个块保存元素的值、起始索引、结束索引和出现次数。例如,上面的例子可以分为四个块:第一个块是-1,出现两次;第二个块是1,出现四次;第三个块是3,出现一次;第四个块是10,出现三次。 接下来,当处理查询区间[l, r]时,需要考虑几种情况。首先,查询区间可能完全包含某个块,这时候该块的次数就是这个区间的最大值。或者,查询区间可能跨越多个块,这时候需要比较左边部分、中间完整块的最大次数,以及右边部分。例如,如果查询区间覆盖了三个块中的部分,那么左边和右边的块可能被部分包含,中间的块则全部被包含。这时候需要分别计算左边块中被包含的次数,右边块中被包含的次数,以及中间所有完整块的最大次数,然后取这三个中的最大值。 那怎么预处理呢?可能需要先遍历数组,将相同的元素分块,并记录每个块的起始和结束位置。然后,对于每个块,记录出现的次数。同时,可能需要构建一个数组,保存每个位置所在的块的信息,这样在查询时,可以快速找到左右边界所在的块。 此外,对于中间完整的块,可以用一个稀疏表(如ST表)来快速查询区间内的最大次数。因为中间块的次数已经被预处理过,使用RMQ可以快速得到最大值。这样,总的时间复杂度可以控制在O(n log n)预处理,每次查询O(1)。 不过,代码实现时需要注意如何处理边界情况。比如,当查询的左右边界在同一个块内时,直接返回r - l + 1即可。如果跨块,则需要分别处理左块、中间块和右块。例如,左块的部分是从l到左块的结束位置,次数为左块结束位置 - l + 1;右块的部分是右块的起始位置到r,次数为r - 右块起始位置 + 1;中间的块则通过ST表查询最大值。 另外,构建块的结构可能需要使用一个结构体数组或者多个数组来保存每个块的start、end、count等信息。同时,对于每个位置,需要记录属于哪个块,这样在查询时可以快速定位。 现在,考虑代码的步骤: 1. 预处理数组,将连续相同的元素分块,记录每个块的起始、结束索引和出现次数。 2. 构建ST表,用于快速查询块区间的最大次数。 3. 对于每个查询[l, r],确定左边界所在的块left_block和右边界所在的块right_block。 4. 如果left_block == right_block,直接返回r - l + 1。 5. 否则,计算左块中的次数(left_count = blocks[left_block].end - l + 1),右块中的次数(right_count = r - blocks[right_block].start + 1),中间块的次数通过ST表查询从left_block+1到right_block-1的最大值。 6. 最终结果为max(left_count, right_count, middle_max)。 这样处理应该可以覆盖所有情况。需要注意数组的索引是从1开始还是从0开始,题目中的示例看起来是从1开始的索引,所以在代码中需要保持一致。 在代码实现时,可能会遇到一些细节问题,比如分块是否正确,ST表的构建是否正确,以及在查询时是否正确地处理了中间块的情况。此外,数组的边界处理也很重要,比如当查询的区间跨越多个块时,中间是否有块存在,或者left_block+1可能大于right_block-1的情况,这时候中间部分不存在,不需要查询ST表。 现在,根据这些思路,可以写出相应的代码。例如,使用C++的话,可能需要用结构体数组保存块的信息,预处理每个块的start、end、count,然后构建log表,ST表,处理查询。 例如,分块的部分: struct Block { int start; int end; int count; }; vector<Block> blocks; 预处理: int current = arr[0]; int start = 0; for (int i = 1; i < n; ++i) { if (arr[i] != current) { blocks.push_back({start, i-1, i - start}); current = arr[i]; start = i; } } blocks.push_back({start, n-1, n - start}); 然后,构建每个位置的块索引数组pos_block,其中pos_block[i]表示位置i属于哪个块。 接下来,构建ST表,其中每个块对应一个值,即该块的count。构建ST表的数组是每个块的count值,这样当查询块区间时,可以得到最大的count。 然后,处理每个查询: 输入l和r(假设1-based),转换为0-based的话可能需要调整。 找到left_block = pos_block[l-1], right_block = pos_block[r-1]. 如果left_block == right_block,返回r - l + 1. 否则,计算left_count = blocks[left_block].end - (l-1) +1 → 因为块结束位置是原数组的索引,比如原数组是0-based的话,比如块的start是0,end是1,那么块的长度是2。例如,假设原数组是-1, -1,块的start=0,end=1,count=2。如果查询的位置是1到2(假设原题是1-based),转换为0-based是0和1,那么块的长度是2,所以返回2。这里可能需要调整索引的转换。 这部分需要仔细处理索引转换是否正确。例如,题目中的示例查询区间是[2,3],在1-based的情况下,对应的数组索引是1和2(假设数组是0-based)。原数组是-1,-1,1,1,1,1,3,10,10,10。那么块的情况: 块0:start=0, end=1, count=2(元素-1) 块1:start=2, end=5, count=4(元素1) 块2:start=6, end=6, count=1(元素3) 块3:start=7, end=9, count=3(元素10) 当查询区间是[2,3](1-based)即数组索引1到2,此时块0的end是1,块1的start是2。所以索引1属于块0,索引2属于块1。所以查询的区间跨越两个块,这时候需要计算左边部分(块0的结束索引是1,所以左边部分在查询中的长度是1-1+1=1?或者原题中的查询是闭区间,所以计算的时候要考虑。 这部分可能需要更仔细的分析。例如,当查询区间是左块和右块相邻的情况,中间没有完整的块,这时候中间部分的最大值可能不存在,此时只需要比较左右部分的值。 此外,构建ST表的时候,每个块的最大次数是块的count,所以当查询中间块的区间时,得到的最大值是这些块中的最大count。 综上,代码的大致结构应该是正确的。但需要注意索引的转换是否正确,比如题目中的输入是否是1-based还是0-based,代码中的处理需要一致。 现在,参考用户提供的引用,发现引用[3]中提到处理类似问题时的预处理方法,比如分块处理。这可能与当前问题相关。例如,POJ 1275中的处理方式可能涉及到分块和统计,虽然具体问题不同,但预处理的思想类似。 最后,代码的测试也很重要,需要测试各种情况,比如全相同元素的数组,查询整个数组,或者跨越多个块的查询。例如,数组全为1,查询任意区间都应返回区间长度。或者数组元素交替但非降序的情况,但根据题目描述数组是非降序的,所以相同元素必须连续。 综上,POJ 3368的解决方案需要预处理分块,利用ST表快速查询中间块的最大次数,并结合左右部分的结果得到最终答案。</think>根据POJ 3368题目的要求,以下是基于非降序数组区间最频繁元素查询的代码实现与解析: ### 解法思路 题目要求查询区间内出现次数最多的元素频率。由于数组是非降序排列,**相同元素连续分布**,可通过以下步骤实现高效查询: 1. **预处理分块**:将连续相同元素分段,记录每段的起始位置、结束位置和出现次数。 2. **构建稀疏表(ST表)**:用于快速查询任意块区间的最大频率。 3. **分情况处理查询**:根据查询区间是否跨多个块,分别计算左半部分、中间完整块和右半部分的频率最大值[^3]。 --- ### 代码实现(C++) ```cpp #include <iostream> #include <vector> #include <cmath> using namespace std; struct Block { int start, end, count; }; vector<Block> blocks; vector<int> log_table, st[20]; vector<int> pos_block; // 预处理分块并构建ST表 void preprocess(const vector<int>& arr) { int n = arr.size(); if (n == 0) return; // 分块 int current = arr[0], start = 0; for (int i = 1; i < n; ++i) { if (arr[i] != current) { blocks.push_back({start, i-1, i - start}); current = arr[i]; start = i; } } blocks.push_back({start, n-1, n - start}); // 构建位置到块的映射 pos_block.resize(n); int block_id = 0; for (int i = 0; i < n; ++i) { if (i > blocks[block_id].end) block_id++; pos_block[i] = block_id; } // 构建ST表 int m = blocks.size(); log_table.resize(m + 1); for (int i = 2; i <= m; ++i) log_table[i] = log_table[i/2] + 1; for (int i = 0; i < 20; ++i) st[i].resize(m); for (int i = 0; i < m; ++i) st[0][i] = blocks[i].count; for (int j = 1; (1 << j) <= m; ++j) { for (int i = 0; i + (1 << j) <= m; ++i) { st[j][i] = max(st[j-1][i], st[j-1][i + (1 << (j-1))]); } } } // 查询区间最大值 int query_max(int l, int r) { if (l > r) return 0; int k = log_table[r - l + 1]; return max(st[k][l], st[k][r - (1 << k) + 1]); } int main() { int n, q; while (cin >> n && n) { cin >> q; vector<int> arr(n); for (int i = 0; i < n; ++i) cin >> arr[i]; blocks.clear(); pos_block.clear(); preprocess(arr); while (q--) { int l, r; cin >> l >> r; l--; r--; // 转换为0-based索引 int lb = pos_block[l], rb = pos_block[r]; if (lb == rb) { cout << (r - l + 1) << endl; continue; } int left_count = blocks[lb].end - l + 1; int right_count = r - blocks[rb].start + 1; int mid_max = query_max(lb + 1, rb - 1); cout << max(max(left_count, right_count), mid_max) << endl; } } return 0; } ``` --- ### 关键步骤说明 1. **分块预处理**:将连续相同元素合并为块,记录每个块的元信息。 2. **ST表构建**:利用动态规划预处理块区间的最大值,实现$O(1)$时间查询。 3. **查询优化**:将区间拆分为左半部分、中间完整块和右半部分,分别计算最大值[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值