poj 3368 统计区间出现次数最多数个数 RMQ

对上升序列如:1 1 2 2 2 3 3 4 5 5 ....... 统计区间出现次数最多数个数。

我们可以构造一个b[]数组,

if(a[i]==a[i-1])b[i]=b[i-1]+1;

else b[i]=1;

这样对上述例子,b[]数组有1 2 1 2 3 1 2 1 1 2

那么对询问区间[l,r],如果l在数与数交界处,那么直接查询l,r区间最大值。

否则要知道与a[l]相同延伸到end,那么这个区间大小end-l+1,与rmq(end+1,r)取最大值就是答案。


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <cstring>
#include <set>
#include <algorithm>
using namespace std;
const int maxn=2e5;
/*
数组下标从1到n,询问[l,r]最小值
*/
int a[maxn];
int b[maxn],c[maxn];
int dp2[maxn][32];
void ini(int n){
    for(int i=1;i<=n;i++)dp2[i][0]=b[i];
    for(int j=1;j<30;j++){
        for(int i=n;i>=1;i--){
            if(i+(1<<j-1)<=n)
            dp2[i][j]=max(dp2[i][j-1],dp2[i+(1<<j-1)][j-1]);
        }
    }
}
inline int rmq2(int l,int r){
    int k=0;
    while(1<<k+1<=r-l+1)k++;
    return max(dp2[l][k],dp2[r-(1<<k)+1][k]);
}
int sol(int l,int r){
    if(l>r)return 0;
    return rmq2(l,r);
}

int main()
{
    int n,m;
    while(~scanf("%d",&n)){
        if(!n)break;
        scanf("%d",&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            if(i==1||a[i]!=a[i-1])b[i]=1;
            else b[i]=b[i-1]+1;
        }
        int p=n;
        for(int i=n;i>=1;i--){
            if(i==n||b[i]==b[i+1]-1)c[i]=p;
            else c[i]=i,p=i;
        }
        ini(n);
        for(int i=0;i<m;i++){
            int l,r;
            scanf("%d%d",&l,&r);
            if(l>r)swap(l,r);
            int ans0=min(c[l],r)-l+1;
            int ans1=sol(min(c[l],r)+1,r);
            printf("%d\n",max(ans0,ans1));
        }
    }
    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、付费专栏及课程。

余额充值