HDU5289 Assignment RMQ / 单调队列

7 篇文章 0 订阅
1 篇文章 0 订阅

要求最大值最小值之差小于k的子序列的个数

本题有两种方法求解:

方法一:单调队列

暴力枚举区间右端点,然后利用左端点不减的性质解决题目。

具体就是使用两个单调队列分别维护最大最小值

上次用这个数据结构还是高一,当时还十分业余,这次用deque好好的实现了一下。

首先注意单调队列这种数据结构,利用单调性有着十分优秀的性质,合理利用可有大作用。

以本题的最大值优先队列维护区间最大值为例

扫描数组,如果当前数字比对手数字大,那么直接插入队头,否则,重点的是,我们不能就此放弃插入这个数字,反之,由于是从前向后扫描数组,那么当加入了目前队头这个最大值之后,比他小的那些元素就全都用不到了,因为这些都比最大值更靠前,所以应该从队尾把比当前元素小的元素全部弹出,然后加入当前元素在队尾

具体实现看代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <deque>
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define maxn 100009
#define ll long long

using namespace std;

int n, k, a[maxn], MaxNum[maxn][21], MinNum[maxn][21];
ll ans = 0;

int main()
{
	int Case;
	scanf ("%d", &Case);
	while (Case--)
	{
		deque <int> qma, qmi;
		ans = 0;
		scanf ("%d%d", &n, &k);
		rep (i, 1, n)
			scanf ("%d", &a[i]);
		int l = 1;
		rep (i, 1, n)
		{
			if (a[i] > qma.front () || qma.empty ())
				qma.clear (), qma.push_front (a[i]);
			else
			{
				while (qma.back () < a[i])
					qma.pop_back ();
				qma.push_back (a[i]);
			}
			if (a[i] < qmi.front () || qmi.empty ())
				qmi.clear (), qmi.push_front (a[i]);
			else
			{
				while (qmi.back () > a[i])
					qmi.pop_back ();
				qmi.push_back (a[i]);
			}
			int Max = qma.front (), Min = qmi.front ();
			while (Max - Min >= k)
			{
				//printf ("---- %d %d %d\n", l, Max, Min);
				if (a[l] == Max)
					qma.pop_front (), Max = qma.front ();
				if (a[l] == Min)
					qmi.pop_front (), Min = qmi.front ();
				l++;
			}
			//printf ("%d %d=%d ===%d %d\n", Max, Min, qmi.size (), l, i);
			ans += (i - l + 1);
		}
		cout << ans << endl;
	}
	return 0;
}

方法二:

我们知道求区间最大最小值可以用logn做到的数据结构有很多

这个不涉及修改

当然就选择最简单的rmq,不多废话了

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define maxn 100009
#define ll long long

using namespace std;

int n, k, a[maxn], MaxNum[maxn][21], MinNum[maxn][21];
ll ans = 0;

void pre ()
{
	rep (i, 1, n)
		MaxNum[i][0] = MinNum[i][0] = a[i];
	rep (j, 1, 20)
		rep (i, 1, n)
			if (i + (1 << j) - 1 <= n)
				MaxNum[i][j] = max (MaxNum[i][j - 1], MaxNum[i + (1 << (j - 1))][j - 1]), MinNum[i][j] = min (MinNum[i][j - 1], MinNum[i + (1 << (j - 1))][j - 1]);
}

int ask_max (int l, int r)
{
	int now = 0;
	while (l + (1 << (now + 1)) - 1 <= r)
		now++;
	return max (MaxNum[l][now], MaxNum[r - (1 << now) + 1][now]);
}

int ask_min (int l, int r)
{
	int now = 0;
	while (l + (1 << (now + 1)) - 1 <= r)
		now++;
	return min (MinNum[l][now], MinNum[r - (1 << now) + 1][now]);
}

int main()
{
	int Case;
	scanf ("%d", &Case);
	while (Case--)
	{
		ans = 0;
		scanf ("%d%d", &n, &k);
		rep (i, 1, n)
			scanf ("%d", &a[i]);
		pre ();
		int now = 1;
		rep (i, 1, n)
		{
			int Max, Min;
			while (1)
			{
				Max = ask_max (now, i);
				Min = ask_min (now, i);
				//printf ("ask %d %d Max %d Min %d\n", i, now, Max, Min);
				if (Max - Min < k)
					break;
				now++;
			}
			ll t = i - now + 1;
			//printf ("%d %d========\n", now, i);
			//ans += (t + 1) * t / 2;
			ans += t;
		}
		cout << ans << endl;
	}
	return 0;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值