poj3368 Frequent values(ST思维题)

题目

有n(n<=1e5)个数字,m(m<=1e5)次询问,

每次询问区间[Li,Ri]中出现最多次数的数字,输出最多次数。

题目保证,对于1<=i<=n,a_{i}<=a_{i+1}

思路来源

https://blog.csdn.net/qq_24489717/article/details/51040122

题解

首先,尺取处理出,对于值v来说,它是第几个出现的v

由于数组是不降的,所以相同的值是挨在一起的

这样处理之后,就可以把询问区间分成两段了,

比如对于出现次数3 4 1 2 1 2 3 1 2 1这样的区间

只有第一个3 4是没有包含区间左端点的,所以,

答案是左边相同的值的区间[3,4]的区间长度 和[1 2 1 2 3 1 2 1]区间的最大值

这个临界点可以二分,二分出来最大的和a[l]相同的值的下标

注意特判,区间只有一个值的情形

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath> 
using namespace std;
const int maxn=1e6+10;
int mx[maxn][22];
int n,m;
int a[maxn],num[maxn];
int l,r,pos,ans;
void ST(int n)
{
	for(int i=1;i<=n;++i)
	mx[i][0]=num[i];
	for(int len=1;(1<<len)<=n;++len)
	{
		for(int i=1;i+(1<<len)-1<=n;i++)
		mx[i][len]=max(mx[i][len-1],mx[i+(1<<(len-1))][len-1]);
	}
}
int RMQ(int l,int r)
{
	int len=log(r-l+1)/log(2);
	return max(mx[l][len],mx[r-(1<<len)+1][len]);
}
int erfen(int l,int r)//最大的==a[l]的下标 
{
	int v=a[l];
	while(l<r)
	{
		int mid=(l+r)/2;
		if(a[mid]==v)l=mid+1;
		else r=mid;
	}
	if(a[l]!=v)l--;
	return l;
}
int main()
{
	while(~scanf("%d",&n)&&n)
	{
		//num[0]=a[0]=0 默认 
		scanf("%d",&m);
		for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
		for(int i=1;i<=n;++i)
		{
			if(a[i]==a[i-1])num[i]=num[i-1]+1;//尺取 连续子段长度 
			else num[i]=1;
		}
		ST(n);
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d",&l,&r);
			pos=erfen(l,r);//[l,pos],[pos+1,r]
			if(pos==r)ans=r-l+1;
			else ans=max(pos-l+1,RMQ(pos+1,r));
			printf("%d\n",ans); 
		}
	}
	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,然后构建logST,处理查询。 例如,分块的部分: 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
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值