2015多校第一场1002(单调队列、STL multiset、)

本文详细介绍了HDU 5289 Assignment问题的两种高效算法解决方案,包括O(nlogn)复杂度的多集插入删除方法和利用单调队列的尺取法思想。通过实例分析,解释了如何在公司人员分配任务时,找到满足特定条件的连续子区间数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5289

Assignment

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 997    Accepted Submission(s): 490


Problem Description
Tom owns a company and he is the boss. There are n staffs which are numbered from 1 to n in this company, and every staff has a ability. Now, Tom is going to assign a special task to some staffs who were in the same group. In a group, the difference of the ability of any two staff is less than k, and their numbers are continuous. Tom want to know the number of groups like this.
 

Input
In the first line a number T indicates the number of test cases. Then for each case the first line contain 2 numbers n, k (1<=n<=100000, 0<k<=10^9),indicate the company has n persons, k means the maximum difference between abilities of staff in a group is less than k. The second line contains n integers:a[1],a[2],…,a[n](0<=a[i]<=10^9),indicate the i-th staff’s ability.
 

Output
For each test,output the number of groups.
 

Sample Input
  
2 4 2 3 1 2 4 10 5 0 3 4 5 2 1 6 7 8 9
 

Sample Output
  
5 28
Hint
First Sample, the satisfied groups include:[1,1]、[2,2]、[3,3]、[4,4] 、[2,3]
 

Author
FZUACM
 

Source
 

题意:

找出连续子区间(满足最大值与最小值的差小于k)的个数。


分析:

方法1:如果直接暴力,复杂度为O(n^2)。而暴力所浪费的时间在于重复算了已经算的区间。比如第一次算得1~10区间都满足,那么2~10肯定也满足,所以以此类推可知,如果一个区间的起点包括在已算的区间内,那么对于这个点的右端点就可以从已算区间的右端点开始往右遍历。然后利用multiset这个STL容器每次添加右边的元素,同时删除左边的元素,那么就可以直接知道区间的最大值与最小值,而插入删除的时间复杂度为O(logn)。如果这样从左到右遍历的话,总共也只需遍历n个点,所以最终时间复杂度为O(nlogn)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int main()
{
    int T, n, k, a[N];

    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n,&k);
        for(int i=0; i<n; i++)
            scanf("%d", a+i);
        multiset<ll> ms;
        multiset<ll>::iterator its, ite;
        ll t = 0, res = 0;
        for(ll i=0; i<n; i++)
        {
            its = ms.begin();
            ite = ms.end();
            if(!ms.empty()) ite--;
            else
            {
                ms.insert(a[i]);
                ite = ms.begin();
                its = ms.begin();
                t++;
            } 
            while(t<n && (*ite)-(*its)<k)
            {
                ms.insert(a[t++]);
                its = ms.begin();
                ite = --ms.end();
            }
            res += t-i;
            ms.erase(ms.find(a[i]));
        }
        printf("%lld\n", res);
    }
    return 0;
}


方法2:

利用单调队列,思想跟上面的差不多,是尺取法的思想。

要特别注意单调队列的特性,假如插入了一个原本单调递增的序列到递增的单调队列中,那么序列不变。如果将其插入到递减的单调队列中,就会只保留值最大的元素。其实也就是队列中保留最大值或最小值,使序列单调,以最新的数为主。

(单调队列好像也被称为双端队列,队列的头和尾都可以插入以及弹出操作。一般用来求区间最值,队列中存的元素都是下标。例如求区间最大值:保证队头元素对应值最大,元素对应值从头到尾单调递减。插入元素时,将要插入元素对应的值与队尾元素对应值比,如果插入元素对应值小就舍去,大的话就弹出队尾元素,再跟现在的队尾元素对应值比,直到队列中没有比要插入元素对应值小的再将要插入元素插入到队尾。相等的话也是要更新的。最大值就是队头。如果队头元素"过期了"就把它从头部弹出。)

#include<stdio.h>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int main()
{
    int T, n, k, a[N];

    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n,&k);
        for(int i=0; i<n; i++)
            scanf("%d", a+i);
        deque<int> q1,q2; //q1递增,q2递减
        ll res = 0, head = 0;
        for(int i=0; i<n; i++)
        {
            while(!q1.empty() && a[q1.back()]>=a[i]) q1.pop_back();
            q1.push_back(i); //这两行是单调队列的元素插入
            while(!q2.empty() && a[q2.back()]<=a[i]) q2.pop_back();
            q2.push_back(i); //插入元素的下标
            while(1)
            {
                int index1 = q1.front();
                int index2 = q2.front();
                int minn = a[index1];
                int maxn = a[index2];
                if(maxn-minn < k) break;
                if(index1 < index2) //当插入的原本序列为递增序列时
                {
                    head = index1+1;
                    q1.pop_front();
                }
                else //如果原本是一个递减序列
                {
                    head = index2+1;
                    q2.pop_front();
                }
            }
            res += i-head+1;
        }
        printf("%lld\n", res);
    }
    return 0;
}



### 单调队列的实现 单调队列是一种特殊的队列结构,其中存储的数据保持有序状态(通常是递增或递减)。通过利用 C++ STL 中的 `deque` 容器以及自定义逻辑,可以高效地实现这一功能。 以下是基于 C++ STL单调队列实现方法: #### 使用 `std::deque` 实现单调队列 下面是一个典型的单调队列实现示例代码,用于维护窗口内的最大值或最小值。此代码展示了如何使用 `std::deque` 来构建一个支持 O(1) 时间复杂度查询最值的操作。 ```cpp #include <iostream> #include <vector> #include <deque> using namespace std; // 维护滑动窗口的最大值 void slidingWindowMaximum(const vector<int>& nums, int k) { deque<int> dq; // 双端队列,用来保存索引 for (int i = 0; i < nums.size(); ++i) { // 移除超出当前窗口范围的元素 while (!dq.empty() && dq.front() < i - k + 1) { dq.pop_front(); } // 从队列尾部移除小于当前元素的所有值,以维持单调性 while (!dq.empty() && nums[dq.back()] <= nums[i]) { dq.pop_back(); } // 将当前元素加入队列 dq.push_back(i); // 输出窗口中的最大值 if (i >= k - 1) { cout << nums[dq.front()] << " "; } } } int main() { vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7}; int k = 3; slidingWindowMaximum(nums, k); return 0; } ``` 上述代码实现了在一个大小固定的滑动窗口中寻找每一步的最大值的功能[^1]。这里的关键在于双端队列 (`std::deque`) 被用来跟踪可能成为候选最大值的元素索引,并且始终保持这些索引对应的数值呈严格递减顺序。 #### 自定义比较函数的应用 如果需要改变默认行为(比如改为求解最小值),可以通过调整条件判断语句完成。例如,在上面的例子中修改 `nums[dq.back()] <= nums[i]` 成为 `nums[dq.back()] >= nums[i]` 则会得到相反的结果——即每次输出的是该区间内的最小值而非最大值[^2]。 另外需要注意的一点是在处理大量输入数据时性能考量方面的问题。尽管标准模板库提供了丰富的容器和算法供开发者选用,但在某些特定场景下仍需权衡效率与易用性的取舍。对于非常庞大的数据集来说,传统的 I/O 函数如 `scanf` 和 `printf` 往往表现得更加迅速;然而当项目规模较小或者开发时间有限的情况下,则完全可以依赖于更简洁直观的标准流操作符 `<`, `>` 进行交互[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值