2015多校第一場1002(單調隊列、STL multiset、)

題目鏈接: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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章