poj 3368 RMQ做法-实际上不过是dp的变形罢了

上一篇是线段树的做法,这一个是用RMQ方法做的,顺便学习一下RMQ。

说是RMQ实际上就是dp的变形。

RMQ定义:

RMQ (Range Minimum/Maximum Query)问题是指:对于长度为n的数列A,回答若干询问RMQ(A,i,j)(i,j<=n),返回数列A中下标在i,j里的最小(大)值,也就是说,RMQ问题是指求区间最值的问题。

主要方法及复杂度如下:

  1、朴素(即搜索),O(n)-O(qn) online。

  2、线段树,O(n)-O(qlogn) online。

  3、ST(实质是动态规划),O(nlogn)-O(1) online。

  ST算法(Sparse Table),以求最大值为例,设d[i,j]表示[i,i+2^j-1]这个区间内的最大值,那么在询问到[a,b]区间的最大值时答案就是max(d[a,k], d[b-2^k+1,k]),其中k是满足2^k<=b-a+1(即长度)的最大的k,即k=[ln(b-a+1)/ln(2)]。

  d的求法可以用动态规划,d[i, j]=max(d[i, j-1],d[i+2^(j-1), j-1])。

  4、RMQ标准算法:先规约成LCA(Lowest Common Ancestor),再规约成约束RMQ,O(n)-O(1) online。

  首先根据原数列,建立笛卡尔树,从而将问题在线性时间内规约为LCA问题。LCA问题可以在线性时间内规约为约束RMQ,也就是数列中任意两个相邻的数的差都是+1或-1的RMQ问题。约束RMQ有O(n)-O(1)的在线解法,故整个算法的时间复杂度为O(n)-O(1)。

 

这里的状态方程dp[j][i] = max(dp[j][i - 1], dp[j + (1 <<(i - 1))][i - 1]);

dp[j][i]标示从j开始前2^i个数中最大重复次数。

有了这些,其他和上一篇基本上一样的,所以代码极其相似。

 

//RMQ
#include <iostream>
#include <cmath>
using namespace std;

int arr[100010],hash[100010];

struct Part
{
	int s, e, cnt;
}part[100010];
int dp[100010][18];//dp[i][j] is for : the max value rang from i to (i + 2^j - 1)

int Max(int a, int b)
{
	return a >  b ? a : b;
}
void Dp(int n)
{
	int i, j, tmp;
	for (i = 1; i <= n; ++ i)
	{
		dp[i][0] = part[i].cnt;
	}
	tmp = log(n + 1.0)/log(2.0);
	for (i = 1; i <= tmp; ++ i)
	{
		for (j = 1; j + (1 << i) - 1 <= n ; ++ j)
		{
			dp[j][i] = Max(dp[j][i - 1], dp[j + (1 << (i - 1))][i - 1]);
		}
	}
}
int getMax(int a, int b)
{
	int k = (int )((log(b - a + 1.0)) / log(2.0));
	return Max(dp[a][k], dp[b - (1 << k) + 1][k]);
}
int main()
{
	int n, q;
	while (scanf("%d", & n) && n)
	{
		scanf("%d", & q);
		for (int i = 1; i <= n; ++ i)
		{
			scanf("%d", & arr[i]);
		}
		memset(part, 0, sizeof(part));
		int id = 1;
		part[1].s = id;
		for (int i = 1; i <= n; ++ i)
		{
			part[id].cnt ++;
			hash[i] = id;
			if (arr[i] != arr[i + 1] || i == n)
			{
				part[id].e = i;
				++ id;
				part[id].s = i + 1;
			}
		}
		Dp(id - 1);
		int a, b;
		while (q --)
		{
			scanf("%d %d", & a, & b);
			if (hash[a] == hash[b])//the same part
			{
				printf("%d\n", b - a + 1);
			}
			else// two situations
			{
				int n1 = part[hash[a]].e - a + 1;
				int n2 = b - part[hash[b]].s + 1;
				int n3 = 0;
				if (hash[b] - hash[a] > 1)
				{
					n3 = getMax(hash[a] + 1, hash[b] -  1);
				}

				printf("%d\n", Max(Max(n1, n2),n3));
			}
		}
	}
	return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值