Codeforces 91B Queue(单调队列 线段树)

There are n walruses standing in a queue in an airport. They are numbered starting from the queue’s tail: the 1-st walrus stands at the end of the queue and the n-th walrus stands at the beginning of the queue. The i-th walrus has the age equal to ai.
The i-th walrus becomes displeased if there’s a younger walrus standing in front of him, that is, if exists such j (i < j), that ai > aj. The displeasure of the i-th walrus is equal to the number of walruses between him and the furthest walrus ahead of him, which is younger than the i-th one. That is, the further that young walrus stands from him, the stronger the displeasure is.
The airport manager asked you to count for each of n walruses in the queue his displeasure.

参考题解
题意:给一个序列,对于第i个数字a[i],在右边找到一个比它小的数,并且最靠右的位置k,输出k-i-1,如果一个都找不到,输出-1。对于序列的每个元素都要输出。
思路
1.单调队列(虽然说是单调队列,但是和单调队列的处理方式有些差别,有类似的思想):我们想要找的是尽可能靠右的比数字小的数的位置,这样考虑的话,要是右边的比当前值小的位置我们提前处理过可以直接用或者可以方便的使用的话就好了,所以,就会想到从后面往前处理,这样后面的情况你已经知道了,再加上存入队列的单调性,那么就可以较为方便的查询了。我们在这里要维护一个严格单调递减的队列,这个很容易想到,但是有一点很让我伤脑筋,那就是,他要的是最右侧的比当前数小的,而不是第一个比他大的数前一个,也就是说从当前数开始一直到最右侧那个比他小的之间,可能还有比他大的,我刚开始想应该怎么处理中间的这些数字,可是都失败了,后来逐渐发现:我们从后往前处理这个序列,那么对于当前这个这个数,之前处理过的比他大的都是对他没有意义的,对以后的数也是,那么就没有让它存活的必要了。也就是说,在我们构造整个单调队列的过程中,如果遇到一个数,这个数比队尾的数字还要小的的时候,就直接入队,而且这个点的结果就是-1;如果这个数比队尾大,那么就在这个队列里面二分搜索第一个比他小的数的位置。搜索完成之后记录答案,然后直接抛弃这个数就可以了。
2.线段树:线段树刚开始也想了,觉得可以写,但是又不知道应该怎么写,还是太菜。我们维护一个最小值的线段树,这样我们直接遍历这个数组就可以了,然后,访问完一个数字,就把这个数更新为正无穷,然后更新线段树,防止对后续查询造成影响。我们用线段树查询的时候与二分的思想十分相像,先看线段树的右孩子,如果右孩子比当前值小,那么优先访问右孩子,否则才访问右孩子,这样就保证了是最右边的比当前值小的位置。

下面挂代码:
单调队列:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5+5;
int data[maxn], que[maxn], ans[maxn];

int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        for(int i = 1; i <= n; i++)    scanf("%d", &data[i]);
        int back = 0;
        for(int i = n; i >= 1; i--)
        {
            if(back == 0 || data[que[back]] >= data[i])
            {
                que[++back] = i;
                ans[i] = -1;
            }
            else
            {
                int result, front = 1, rear = back;
                while(front <= rear)
                {
                    int mid = (front+rear)>>1;
                    if(data[que[mid]] < data[i])
                    {
                        result = mid;
                        rear = mid-1;
                    }
                    else front = mid+1;
                }
                ans[i] = que[result]-i-1;
            }
        }
        for(int i = 1; i <= n; i++)
            printf("%d ", ans[i]);
        printf("\n");
    }
    return 0;
}

线段树:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5+5, INF = 0x3f3f3f3f;
int data[maxn], ans[maxn], tree[maxn<<2];

void build(int l, int r, int cur)
{
    if(l == r)
    {
        scanf("%d", &data[l]);
        tree[cur] = data[l];
        return ;
    }
    int mid = (l+r)>>1;
    build(l, mid, cur<<1);
    build(mid+1, r, cur<<1|1);
    tree[cur] = min(tree[cur<<1], tree[cur<<1|1]);
}

void update(int l, int r, int pos, int cur)
{
    if(l == r)
    {
        tree[cur] = INF;
        return ;
    }
    int mid = (l+r)>>1;
    if(pos <= mid)  update(l, mid, pos, cur<<1);
    else update(mid+1, r, pos, cur<<1|1);
    tree[cur] = min(tree[cur<<1], tree[cur<<1|1]);
}

void query(int l, int r, int cur, int pos)
{
    if(l == r)
    {
        ans[pos] = l-pos-1;
        return ;
    }
    int mid = (l+r)>>1;
    if(tree[cur<<1|1] < data[pos])  query(mid+1, r, cur<<1|1, pos);
    else query(l, mid, cur<<1, pos);
}

int main()
{
    int n;
    while(~scanf("%d", &n))
    {
        build(1, n, 1);
        for(int i = 1; i <= n; i++)
        {
            if(tree[1] >= data[i])  ans[i] = -1;
            else query(1, n, 1, i);
            update(1, n, i, 1);
        }
        for(int i = 1; i <= n; i++)
            printf("%d ", ans[i]);
        printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_40788897/article/details/96746063
今日推荐