单调队列入门——POJ - 2823,HDU - 3530,HDU - 2430


单调队列,顾名思义,维护的队列中的数据是呈单调递增或者单调递减的,在维护单调队列时,例如单调递增队列,当有新的数据入队,则从队尾开始,把所有大于该数的项目pop出队列,保证了这个数据入队后整个队列依旧是单调递增的,一般单调队列的实现是用一个定长数组和变量head和tail来维护队头和队尾。单调队列一个简单应用是定长区间的最大/最小值问题,又叫做窗口滑动,就是一个长度为k的窗口在数列上滑动,每滑动一格就要求出当前窗口内的最大值/最小值。对于这种题目,我们需要做的就是维护单调队列,并且即时把队头元素所处数列位置pos小于当前窗口左端的值弹出队列,这样每次维护完后,单调队列的队头就是当前窗口区间的最大值/最小值了。因此还需要记录队列中每个元素的位置。

单调队列的应用还是很多的,可以对一些算法进行改进已优化复杂度,例如单调队列背包dp,不过我都还不会= =。

单调队列很多时候做的事跟RMQ很像,不过RMQ可以查找不定长区间的最大最小值,不过单调队列的复杂度更低。

详情看题来学习吧。


1.Sliding Window POJ - 2823

应该是最经典的单调队列的应用了,方法我上面说过了。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1000005;
const int maxn = 1000055;
const int M = 25;
int n, p, k;
int num[maxn];
int minn[maxn];
int maxx[maxn];
pair<int, int> que1[maxn];
pair<int, int> que2[maxn];
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++){
        scanf("%d", &num[i]);
    }
    int head1 = 0,head2=0, tail1 = 0,tail2=0;
    for (int i = 0; i < n; i++){
        while (i-que1[head1].second >=k&&head1while (tail1>head1&&num[i]>=que1[tail1 - 1].first){ tail1--; }
        que1[tail1].first = num[i], que1[tail1++].second = i;
        maxx[i] = que1[head1].first;

        while (i - que2[head2].second >= k&&head2while (tail2 > head2&&num[i] <= que2[tail2 - 1].first){ tail2--; }
        que2[tail2].first = num[i], que2[tail2++].second = i;
        minn[i] = que2[head2].first;
    }
    for (int i = k - 1; i < n; i++){
        printf("%d", minn[i]);
        if (i < n - 1){ printf(" "); }
    }
    printf("\n");
    for (int i = k - 1; i < n; i++){
        printf("%d", maxx[i]);
        if (i < n - 1){ printf(" "); }
    }
    return 0;
}

2.Subsequence HDU - 3530

题目要求我们找到一个最大的子序列,使得序列中的最大值减去最小值大于等于m且小于等于k,朴素的做法我们就应该枚举每一个点i作为子序列的末尾,然后往前遍历维护最大值和最小值,来寻找满足条件的最左端,不过n^2的复杂度会超时,这时我们就可以选择使用两个单调队列来维护最大值和最小值,首先,如果递减单调队列的值减去递增单调队列的值大于k,那么使i作为最右端的满足条件的序列的最左端肯定在两个队头所处位置左者的右边,枚举到后面的i作为右端时也如此,所以这里弹出队头是不会影响后续处理的,通过维护单调队列和最左端pre,我们就可以找到满足最大值和最小值差<=k的子序列,这时就可以判断是否大于等于m来更新答案。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1000005;
const int maxn = 100005;
const int M = 25;
int n, m, k;
int num[maxn];
int que1[maxn], que2[maxn];

int main() {
    int x;
    while (~scanf("%d%d%d", &n, &m, &k)){
        for (int i = 0; i < n; i++){
            scanf("%d", &num[i]);
        }
        int ans = 0;
        int pre = 0;
        int head1 = 0, tail1 = 0,head2=0,tail2=0;
        for (int i = 0; i < n; i++){
            while (head1 < tail1&&num[i] > num[que1[tail1 - 1]])tail1--;
            while (head2 < tail2&&num[i] < num[que2[tail2 - 1]])tail2--;
            que1[tail1++] = i;
            que2[tail2++] = i;
            while (head1 < tail1&&head2k){
                if (que1[head1] < que2[head2]){ pre = que1[head1++] + 1; }
                else{ pre = que2[head2++] + 1; }
            }
            if (head1 < tail1&&head2 < tail2&&num[que1[head1]] - num[que2[head2]] >= m){
                ans = max(ans, i - pre + 1);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

3.Beans HDU - 2430

题目简单来讲就是找一个子序列,使得(序列数值和%P<=K)且(数值和/P)最大,我们先处理出1-i(i=1,2,3…)的数值和sum[i]和模p后的值a[i],对于每一个a[i],若a[i]<=k,则1-i这段子序列是满足条件的,否则,我们需要从1~i-1之间需要一个a[j],使得a[i]-a[j]<=k,朴素做法也就是遍历搜索,不过同上面的题一样,可以使用单调队列来完成,将a[i]从小到大的枚举,维护一个模值单调递增且相同模值时位置单调递增的单调队列,对于每个a[i],不断弹出队头使得队头的模值a[que[head]]满足a[i]减去它小于等于k,就可以更新答案,可以证明这一定是满足调减的最左端(因为按位置排序了)并且弹出的队头对后面的处理无影响(因为枚举的a[i]是单调递增).

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
const int mod = 1000000007;
const int maxm = 1000005;
const int maxn = 1000500;
const int M = 25;
int n, p, k;

struct pp{
    int pos, num;
}a[maxm],b[maxm];
ll sum[maxn];

bool cmp(pp a, pp b){
    if (a.num == b.num)return a.pos < b.pos;
    return a.num < b.num;
}

int main() {
    int t,x;
    scanf("%d", &t);
    for (int cas = 1; cas <= t; cas++){
        scanf("%d%d%d", &n, &p, &k);
        for (int i = 1; i <= n; i++){
            scanf("%d", &x);
            sum[i] = sum[i - 1] + x;
            a[i].num = sum[i] % p;
            a[i].pos = i;
        }
        sort(a + 1, a + n + 1, cmp);
        int head = 0, tail = 0;
        ll ans = -1;
        for (int i = 1; i <= n; i++){
            while (tail > head&&a[i].pos < b[tail-1].pos){ tail--; }
            b[tail++] = a[i];
            while (headk){ head++; }
            if (a[i].num <= k){ ans = max(ans, sum[a[i].pos]/p); continue; }
            if (b[head].posprintf("Case %d: %lld\n", cas, ans);
    }
    return 0;
}

刚学习这个,理解还不是很深刻, 题目有点变形就反应不过来了,讲解也讲不了很清楚,有待深入学习。


你可能感兴趣的:(数据结构)