POJ 2823 Sliding Window (线段树/单调队列)

题目不说了,可以用线段树或者单调队列,下面附上代码。

线段树:

#include <iostream>
#include <stdio.h>
#include <algorithm>
/*
AC
线段树每个节点存储对应区间的最大值、最小值,然后直接查询即可。6594MS。
*/
using namespace std;
const int maxn=1000005;
const int INF=0x3f3f3f3f;
int minans[maxn],maxans[maxn];
int small,bigger;
int n,k;
struct Node{
    int maxv,minv;
}tree[maxn<<2];

void pushUp(int rt){
    tree[rt].maxv=max(tree[rt<<1].maxv,tree[rt<<1|1].maxv);
    tree[rt].minv=min(tree[rt<<1].minv,tree[rt<<1|1].minv);
}

void build(int rt,int L,int R){
    if(L==R){
        scanf("%d",&tree[rt].minv);
        tree[rt].maxv=tree[rt].minv;
        return;
    }
    int mid=(L+R)>>1;
    build(rt<<1,L,mid);
    build(rt<<1|1,mid+1,R);
    pushUp(rt);
}

void query(int rt,int l,int r,int L,int R){
    if(l<=L && R<=r){
        small=min(small,tree[rt].minv);
        bigger=max(bigger,tree[rt].maxv);
        return;
    }
    int mid=(L+R)>>1;
    if(r<=mid)
        query(rt<<1,l,r,L,mid);
    else if(l>mid)
        query(rt<<1|1,l,r,mid+1,R);
    else{
        query(rt<<1,l,r,L,mid);
        query(rt<<1|1,l,r,mid+1,R);
    }
}
int main()
{
    int idx=0;
    scanf("%d%d",&n,&k);
    build(1,1,n);
    for(int i=1;i<=n-k+1;i++){
        bigger=-INF;
        small=INF;
        query(1,i,i+k-1,1,n);
        minans[idx]=small;  //存储最小值序列
        maxans[idx]=bigger; //存储最大值序列
        idx++;
    }
    for(int i=0;i<idx;i++){
        if(i==0)
            printf("%d",minans[i]);
        else
            printf(" %d",minans[i]);
    }
    printf("
");
    for(int i=0;i<idx;i++){
        if(i==0)
            printf("%d",maxans[i]);
        else
            printf(" %d",maxans[i]);
    }
    printf("
");
    return 0;
}

单调队列:

#include <iostream>
#include <stdio.h>
/*
AC 5204ms
利用单调队列,分两次处理,第一次求最小值序列,第二次求最大值序列。

以求最小值序列为例:
先处理前k-1个元素,每次从队尾加入队列之前,先与队尾元素比较,若比队尾元素值小,则删去队尾元素,直到有比它小的,或者队列为空,
将该元素插入到队尾。即每次保证队列为单调非减队列,队首元素始终是队列中最小的。

接下来依次读入一个元素,与队尾元素比较,若队尾元素大,则删去队尾元素,直至遇到比要插入的元素小或者队列为空为止,插入该元素。
由于要求的是连续的k个区间,所以还要把队列中在该区间之前的元素删去。
这里有个技巧,也就是队列中,存储的不是元素值,而是在数组中的位置,这样很容易判断某个元素是否在连续的k个区间内,
获取元素值的话,就通过存储的数组下标获取。

求最大值序列的时候,同最小值序列,保证队列为非增队列,即队列的首个元素一定是最大的。

*/
using namespace std;
const int maxn=1000005;
int n,k;
int a[maxn]; //n个元素值
int dequeue[maxn]; //单调队列
//求最小值序列
void minans(int k,int n) {
    int head=1;
    int tail=0;
    //先处理前k-1个元素
    for(int i=0; i<k-1; i++) {
        while(head<=tail  &&  a[dequeue[tail]]>=a[i]) {
            tail--;
        }
        tail++;
        dequeue[tail]=i;  //存储的是元素在a中的索引
    }
    for(int i=k-1; i<n; i++) {
        while(head<=tail  && a[dequeue[tail]]>=a[i]) {
            tail--;
        }
        tail++;
        dequeue[tail]=i;
        while(dequeue[head]<i-k+1)  //将索引在i-k+1之前的元素删去
            head++;
        printf("%d ",a[dequeue[head]]);  //输出队列首个元素,即最小的元素

    }
}
//求最大值序列
void maxans(int k,int n) {
    int head=1;
    int tail=0;
    for(int i=0; i<k-1; i++) {
        while(head<=tail  &&  a[dequeue[tail]]<=a[i]) {
            tail--;
        }
        tail++;
        dequeue[tail]=i;
    }
    for(int i=k-1; i<n; i++) {
        while(head<=tail  && a[dequeue[tail]]<=a[i]) {
            tail--;
        }
        tail++;
        dequeue[tail]=i;
        while(dequeue[head]<i-k+1)
            head++;
        printf("%d ",a[dequeue[head]]);


    }
}

int main() {
    scanf("%d%d",&n,&k);
    for(int i=0; i<n; i++) {
        scanf("%d",&a[i]);
    }
    minans(k,n);
    printf("
");
    maxans(k,n);
    //printf("
");
    return 0;
}
原文地址:https://www.cnblogs.com/chenxiwenruo/p/3395397.html