【POJ 3368】 Frequent values(RMQ)

【POJ 3368】 Frequent values(RMQ)

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 15813   Accepted: 5749

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

Ulm Local 2007

题目意思比较明了,给出一个长n的有序数组,固定是升序。之后q次查询,每次询问区间[l,r]中出现的最长连续相同序列的长度。

刚开始想直接上ST算法,发现不是很直接的ST,需要一些辅助的东西来变换。就直接先写了发线段树。

发现线段树思路很清晰,先用num数组存下n个数的值。

对于区间[L,R]存三个值

mx:当前区间中最大的连续相同序列长度,也就是答案。

lmx:从左端点开始往右能找到的最大的相同序列长度。

rmx:从右端点开始往左能找到的最大的相同序列长度。

这样就可以做递归的初始化了,对于叶子来说 三个值一样  都是1,因为只有当前位置这一个数

对于区间[L,R] 可由[L,MID] [MID+1,R]组合

首先[L,R]的mx是两个子区间mx中大的一个

如果num[MID] == num[MID+1] 说明左右子区间中间可以连接 [L,R]的mx还要跟[L,MID].r+[MID+1,R].l比较 存较大的一个

如果[L,MID].l == MID-L+1,也就是左子区间中的数全是相同的,[L,R].l = [L,MID].l+[MID+1,R].l。否则 [L,R].l = [L,MID].l

同理 如果[MID+1,R].r == R-MID,也就是右子区间中的数全是相同的,[L,R].r = [MID+1,R].r+[L,MID].r。否则 [L,R].r = [MID+1,R].r

这样线段树的初始化就完成了

对于询问来说 询问[l,r]区间的答案

如果当前区间[L,R] MID >= r 或者MID+1 <= l 就正常跑左子树或右子树

否则 就要找左右两边递归出的较大值 另外 还要考虑num[MID] == num[MID+1]的情况 再跟左区间右端点开始的最长序列+右区间左端点开始的最长序列长度比较一下 选一个较大的即可 此时还要对左右区间的端点开始序列长度进行一些切割 越出的就去掉,最后得到的就是所求的答案

线段树思路比较好想 但写起来略繁琐 可能出现各种错误

提供份自己的代码,大家可参考下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("in.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;

struct Tree
{
	int l,r,mx;
};

//线段树
Tree bit[433333];
//存放n个数
int a[233333];

void init(int root,int l,int r)
{
	if(l == r)
	{
		scanf("%d",&a[l]);
		bit[root].mx = bit[root].l = bit[root].r = 1;
		return;
	}
	int mid = (l+r)>>1;
	init(root<<1,l,mid);
	init(root<<1|1,mid+1,r);

	bit[root].mx = max(bit[root<<1].mx,bit[root<<1|1].mx);

	//如果左右子区间中间可连接,进行一些选取
	if(a[mid] == a[mid+1])
	{
		bit[root].mx = max(bit[root].mx,bit[root<<1].r+bit[root<<1|1].l);
		if(bit[root<<1].l == mid-l+1) bit[root].l = bit[root<<1].l+bit[root<<1|1].l;
		else bit[root].l = bit[root<<1].l;

		if(bit[root<<1|1].r == r-mid) bit[root].r = bit[root<<1|1].r+bit[root<<1].r;
		else bit[root].r = bit[root<<1|1].r;
	}
	else
	{
		bit[root].l = bit[root<<1].l;
		bit[root].r = bit[root<<1|1].r;
	}
}

int Search(int root,int l,int r,int ll,int rr)
{
	if(l == ll && r == rr)
		return bit[root].mx;

	int mid = (l+r)>>1;
	if(mid >= rr) return Search(root<<1,l,mid,ll,rr);
	else if(mid+1 <= ll) return Search(root<<1|1,mid+1,r,ll,rr);
	else
	{
		int tmp = 0;
		if(a[mid] == a[mid+1]) tmp = min(mid-ll+1,bit[root<<1].r)+min(rr-mid,bit[root<<1|1].l);
		return max(max(Search(root<<1,l,mid,ll,mid),Search(root<<1|1,mid+1,r,mid+1,rr)),tmp);
	}
}

int main()
{
	//fread();
	//fwrite();

	int n,m,l,r;
	while(~scanf("%d",&n) && n)
	{
		scanf("%d",&m);
		init(1,1,n);
		while(m--)
		{
			scanf("%d%d",&l,&r);
			printf("%d\n",Search(1,1,n,l,r));
		}
	}

	return 0;
}

今天又想了下ST的写法,大体讲一下,可能讲的不是很明白,大家谅解~

不过提交发现跟线段树相比就优化了几百MS(其实也蛮多了,毕竟2000MS时限,。

大体思路就是在存放数值的数组num之外,再开一个辅助数组f 表示从当前位置往后最多能连续到的位置

比如这个数据:

10 3
-1 -1 1 1 1 1 3 3 10 10

对应的f数组就是

 2  2 6 6 6 6 7 7 10 10

对于rmq数组 我的写法是初始化时允许越出 就是只存储当前区间中出现过的数往后延伸出的最大的的长度,超出界限也允许。

这样在查询时需要加一些特殊处理,可能是导致时间不是很理想的原因。

初始化跟普通的rmq一样 就不详讲了

对于查询区间[L,R] 存在三种情况:

1.f[L] >= R 就是整个区间都是连续相同 类似【这种状态 这样答案就是R-L+1

2.f[f[L]+1] >= R 就是刚好两半的情况 类似【】【 这种状态 譬如上面数据中查询[4,7] 刚好是两种数 输出答案就是f[L]-L+1和R-f[L]中较大的一个

3.其余情况,就是类似 【】【】【】【】【 这种状态 其实会发现上面两种都是这种情况的延伸,其实就是两个很细小的剪枝,不过也省去了一些区间为负的特判。

对于这种情况 就需要二分出最后一个残缺的区间的左端点,因为在最开始提到 这个我写的这个ST的数组允许越出,对于右边界需要特殊处理。我想到的是二分。。所以这里可能会多一个nlogn 这样找到右边那个残缺区间的左端点后就好做了 求一下完整区间的RMQ 然后与右部的长度选一个较大的,即为答案

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread() freopen("in.in","r",stdin)
#define fwrite() freopen("out.out","w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;

int num[233333];
int f[233333];
int rmq[233333][19];
int n;

void init()
{
	for(int i = 1; i <= n; ++i)
		scanf("%d",&num[i]);

	for(int i = n; i >= 1; --i)
		if(i != n && num[i] == num[i+1]) f[i] = f[i+1];
		else f[i] = i;

	for(int k = 0; k <= 18; ++k)
		for(int i = 1; i <= n && i+(1<<k)-1 <= n; ++i)
			if(!k) rmq[i][k] = f[i]-i+1;
			else rmq[i][k] = max(rmq[i][k-1],rmq[i+(1<<(k-1))][k-1]);
}

int RMQ(int l,int r)
{
	int k = log((r-l+1)*1.0)/log(2.0);
	return max(rmq[l][k],rmq[r-(1<<k)+1][k]);
}

int main()
{
	//fread();
	//fwrite();

	int q,l,r;
	while(~scanf("%d",&n) && n)
	{
		scanf("%d",&q);
		init();
		while(q--)
		{
			scanf("%d%d",&l,&r);
			if(f[l] >= r) printf("%d\n",r-l+1);
			else if(f[f[l]+1] >= r) printf("%d\n",max(f[l]-l+1,r-f[l]));
			else
			{
				int ans = -1;
				int ll = f[l]+1,rr = r;
				while(ll <= rr)
				{
					int mid = (ll+rr)>>1;
					if(f[mid] == f[r])
					{
						ans = mid;
						rr = mid-1;
					}else ll = mid+1;
				}
				printf("%d\n",max(RMQ(l,ans-1),r-ans+1));
			}
		}
	}

	return 0;
}
时间: 2024-10-08 20:50:25

【POJ 3368】 Frequent values(RMQ)的相关文章

UVA 11235 Frequent values(RMQ)

Frequent values TimeLimit:3000Ms 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 f

【POJ 1191】 棋盘分割(DP)

[POJ 1191] 棋盘分割(DP) Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 13811   Accepted: 4917 Description 将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘.(每次切割都只能沿着棋盘格子的边进行) 原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分

POJ 3368 Frequent values(RMQ)

Frequent values Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 15134   Accepted: 5519 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 cons

BZOJ 2296【POJ Challenge】随机种子(构造)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2296 [题目大意] 给出一个数x,求一个10的16次以内的数使得其被x整除并且数字包含0到9所有数字 [题解] 我们构造数前10位为0到9,利用后面六位补充使得其为x的倍数,x为10的6次方以内, 因此除x=0的特殊情况外一定存在解. [代码] #include <cstdio> using namespace std; int T,x; int main(){ scanf(&qu

UVA-11235 Frequent values (RMQ)

题目大意:在一个长度为n的不降序列中,有m次询问,每次询问(i,j)表示在区间(i,j)中找出出现次数最多的元素的出现次数. 题目分析:因为序列有序,可以将序列分段,并且记录每段的元素个数.每一个元素所属的段num(i).每一个元素所属段的左端点l(i)及右端点r(i).那么对于每次询问: ans(i,j)=max(max(r(i)-i+1,j-l(j)+1),rmq(num(i)+1,num(j)-1)). ans(i,j)=r-j+1 (如果i与j属于同一段) 代码如下: # include

POJ 3368:Frequent values(线段树区间合并)

题目大意,给出一段非降序列,求一些区间中出现频率最高的数的出现次数. 分析: 显然,区间中一个数多次出现必然是连续的,也就是最长的连续相等的一段. 用线段树解决,维护三个信息:一个区间最长连续的区间的长度(即要求的答案),以区间左端点为起点的最长连续区间的长度,以区间右端点为终点最长连续区间的长度.通过这三个信息,我们可以对合并快速处理

【POJ - 1321】棋盘问题 (dfs)

棋盘问题 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C. Input 输入含有多组测试数据. 每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目. n <= 8 , k <= n 当为-1 -1时表示输入结束. 随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, .

【POJ - 3669】Meteor Shower(bfs)

-->Meteor Shower 直接上中文了 Descriptions: Bessie听说有场史无前例的流星雨即将来临:有谶言:陨星将落,徒留灰烬.为保生机,她誓将找寻安全之所(永避星坠之地).目前她正在平面坐标系的原点放牧,打算在群星断其生路前转移至安全地点. 此次共有M (1 ≤ M ≤ 50,000)颗流星来袭,流星i将在时间点Ti (0 ≤ Ti  ≤ 1,000) 袭击点 (Xi, Yi) (0 ≤ Xi ≤ 300; 0 ≤ Yi ≤ 300).每颗流星都将摧毁落点及其相邻四点的区

【POJ - 2718】Smallest Difference(搜索 )

-->Smallest Difference 直接写中文了 Descriptions: 给定若干位十进制数,你可以通过选择一个非空子集并以某种顺序构建一个数.剩余元素可以用相同规则构建第二个数.除非构造的数恰好为0,否则不能以0打头. 举例来说,给定数字0,1,2,4,6与7,你可以写出10和2467.当然写法多样:210和764,204和176,等等.最后一对数差的绝对值为28,实际上没有其他对拥有更小的差. Input  输入第一行的数表示随后测试用例的数量.对于每组测试用例,有一行至少两个