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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于HDU4546问题,还可以使用优先队列(Priority Queue)来解决。以下是使用优先队列的解法思路: 1. 首先,将数组a进行排序,以便后续处理。 2. 创建一个优先队列(最小堆),用于存储组合之和的候选值。 3. 初始化优先队列,将初始情况(即前0个数的组合之和)加入队列。 4. 开始从1到n遍历数组a的元素,对于每个元素a[i],将当前队列中的所有候选值取出,分别加上a[i],然后再将加和的结果作为新的候选值加入队列。 5. 重复步骤4直到遍历完所有元素。 6. 当队列的大小超过k时,将队列中的最小值弹出。 7. 最后,队列中的所有候选值之和即为前k小的组合之和。 以下是使用优先队列解决HDU4546问题的代码示例: ```cpp #include <iostream> #include <vector> #include <queue> #include <functional> using namespace std; int main() { int n, k; cin >> n >> k; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } sort(a.begin(), a.end()); // 对数组a进行排序 priority_queue<long long, vector<long long>, greater<long long>> pq; // 最小堆 pq.push(0); // 初始情况,前0个数的组合之和为0 for (int i = 0; i < n; i++) { long long num = pq.top(); // 取出当前队列中的最小值 pq.pop(); for (int j = i + 1; j <= n; j++) { pq.push(num + a[i]); // 将所有加和结果作为新的候选值加入队列 num += a[i]; } if (pq.size() > k) { pq.pop(); // 当队列大小超过k时,弹出最小值 } } long long sum = 0; while (!pq.empty()) { sum += pq.top(); // 求队列中所有候选值之和 pq.pop(); } cout << sum << endl; return 0; } ``` 使用优先队列的方法可以有效地找到前k小的组合之和,时间复杂度为O(nklog(k))。希望这个解法对你有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值