【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


题目意思比较明了,给出一个长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;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值