hdu 5289 Assignment (四种解法)

Assignment

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

 

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]

题目大意:给出一个数列,问其中存在多少连续子序列,子序列的最大值-最小值< k 
四种解法:

1:multiset维护+双指针

2:单调队列维护+双指针

3:RMQ+双指针

4:RMQ+二分

直接暴力,肯定GG, 此题关键就是如何快速得出(L,R)区间的最大和最小值,然后交给了双指针或者二分法。

如何快速得出(L,R)区间的最值,就要靠multiset,单调队列,RMQ这三种数据结构了。

第一种结构最简单,因为它保存了(L,R)区间所有值而且还排了顺序,直接取出第一个和最后一个即可。

第二种结构得需要两个单调队列分别存(L,R)区间的(一系列的最大值、次小值等等)和(一系列最小值、次小值等等)

当然头部就是我们要的最值了。

第三种结构,裸RMQ预处理一下,得出每个区间的最大值和最小值。

比起把这题归为RMQ算法或者单调队列算法,我更喜欢把它归为双指针或者二分法,个人觉得multiset,RMQ,单调队列抑或是线段树他们只是一种数据结构,若是被它们主导你了你的想法,那就很难发现这题的乐趣,也很难在意他们之外的东西(比如其他思考的方式),哈哈个人拙见。这三种结构都不难,就不给详细解析了,后面三种借用了AC_XXZ博主的文章的代码了,把表达不准确或有误修正了下,偷个懒。

方法一:

#include <iostream>
#include <stdio.h>
#include <set>
using namespace std;
#define N 100005
int main()
{
    int t,a[N];
    scanf("%d",&t);
    int n,k;
    while(scanf("%d%d",&n,&k)!=EOF){
        for (int i=0;i<n;i++)
            scanf("%d",&a[i]);
        int j=0;
        multiset <int> s;
        long long ans=0;
        for (int i=0;i<n;i++){
            s.insert(a[i]);
            multiset<int>::iterator it1,it2;
            while(true){
                it1=s.begin();
                it2=s.end();
                it2--;
                if((*it2)-(*it1)<k)break;
                s.erase(s.find(a[j++]));
            }
            ans+=i-j+1;
        }
        cout<<ans<<endl;
    }
    return 0;
}

方法二:

单调队列 
用单调队列维护最大值最小值,双指针,第一个第二个指针初始指向第一个数据,第一个指针按顺序不断向队尾添加数据,当最大值最小值的差大于等于k后,就意味着新添加的这个不能作用于当前第二个指针的位置,也就能计算出,以第二个指针位置开始的连续子序列的个数,最后统计总和。

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std ;
#define LL long long
deque <LL> Max , Min ;
//单调队列,Max最大值,Min最小值
LL a[100010] ;
int main()
{
    int T , n , i , j ;
    LL k , ans ;
    scanf("%d", &T) ;
    while( T-- )
    {
        scanf("%d %I64d", &n, &k) ;
        for(i = 0 ; i < n ; i++)
            scanf("%I64d", &a[i]) ;
        while( !Max.empty() ) Max.pop_back() ;
        while( !Min.empty() ) Min.pop_back() ;
        for(i = 0 , j = 0 , ans = 0; i < n ; i++)  //j在前,i在后
        {
            while( !Max.empty() && Max.back() < a[i] ) Max.pop_back() ;
            Max.push_back(a[i]) ;
            while( !Min.empty() && Min.back() > a[i] ) Min.pop_back() ;
            Min.push_back(a[i]) ;
            while( !Max.empty() && !Min.empty() && Max.front() - Min.front() >= k )
            {
                ans += (i-j) ;
                if( Max.front() == a[j] ) Max.pop_front() ;
                if( Min.front() == a[j] ) Min.pop_front() ;
                j++ ;
            }
        }
        while( j < n )
        {
            ans += (i-j) ;
            j++ ;
        }
        printf("%lld\n", ans) ;
    }
    return 0 ;
}

方法三:RMQ+双指针 
这种枚举右端点,贪心选取右端点(类似尺取法)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cmath>
using namespace std;

int  maxsum[100000][30];
int minsum[100000][30];

int a[100000];
int n,m;
void rmq_init()
{
    for(int j = 1; (1<<j) <= n; ++j)
        for(int i = 1; i + (1<<j) - 1 <= n; ++i)
        {
            maxsum[i][j] = max(maxsum[i][j-1],maxsum[i+(1<<(j-1))][j-1]);
            minsum[i][j] = min(minsum[i][j-1],minsum[i+(1<<(j-1))][j-1]);
        }

}

int query(int l, int r)
{
    int k = log2(r-l+1);
    int Max = max(maxsum[l][k], maxsum[r-(1<<k)+1][k]);
    int Min = min(minsum[l][k], minsum[r-(1<<k)+1][k]);
    return Max - Min;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i = 1; i <= n; ++i)
        {
            scanf("%d",a+i);
            maxsum[i][0] = minsum[i][0] = a[i];
        }
        rmq_init();

        long long  ans = 0;
        int k=1;
        for(int i=1; i<=n; i++)
        {
            while(query(k,i)>=m&&k<i)k++;
            ans+=(i-k+1);
        }

        printf("%lld\n",ans);

    }
    return 0;
}

方法四:

RMQ+二分 
RMQ维护最大值,最小值,枚举左端点i,二分找出最远的符合的右端点j,答案就是ans += j - i+1;(手推一下就知道) 
比如1 2 3 
含有i的有三种 

1 2 
1 2 3 
其它的2,2 3,3下面i=2的时候会算的,所以每次加j-i+1就行

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cmath>
using namespace std;

int  maxsum[100000][30];
int minsum[100000][30];

int a[100000];
int n,k;
void rmq_init()
{
    for(int j = 1; (1<<j) <= n; ++j)
        for(int i = 1; i + (1<<j) - 1 <= n; ++i)
        {
            maxsum[i][j] = max(maxsum[i][j-1],maxsum[i+(1<<(j-1))][j-1]); 
            minsum[i][j] = min(minsum[i][j-1],minsum[i+(1<<(j-1))][j-1]);
        }

}

int query(int l, int r)
{
    int k = log2(r-l+1);
    int Max = max(maxsum[l][k], maxsum[r-(1<<k)+1][k]);
    int Min = min(minsum[l][k], minsum[r-(1<<k)+1][k]);
    return Max - Min;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        for(int i = 1; i <= n;++i)
        {
            scanf("%d",a+i);
            maxsum[i][0] = minsum[i][0] = a[i];
        }
        rmq_init();

        long long  ans = 0;
        int l , r;
        for(int i = 1; i <= n; ++i)
        {
            l = i , r = n;

            while(l <= r)
            {
                int mid = (l+r)/2;
                int cha = query(i,mid);
                if(cha < k) l = mid+1;
                else r = mid - 1;
            }

            ans += l - i;
        }

        printf("%lld\n",ans);

    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zero_zp/article/details/81274214