一種神奇的算法——單調隊列


單調隊列,顧名思義,就是一種隊列。

在進入正文中,我們先來看這么一個問題:傳送門

現在有一堆數字共N個數字(N<=10^6),以及一個大小為k的窗口。現在這個從左邊開始向右滑動,每次滑動一個單位,求出每次滑動后窗口中的最大值和最小值。

例如:

The array is [1 3 -1 -3 5 3 6 7], and k = 3

我們看到題面后,首先一個思路:暴力枚舉!我們只要在\(k-n\)個數中枚舉每一個最大值和最小值,最后取\(min\)或取\(max\)即可,代碼如下

#include<iostream>
using namespace std;
int n,k;
int a[3000003];
int f[3000003];
int read() {
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-') f=-f;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int main() {
	n=read();k=read();
	int l=0;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=k;i<=n;i++) {
		int maxn=-0xfffff,minx=0xfffff;
		for(int j=i-k+1;j<=i;j++) {
			if(a[j]>maxn) maxn=a[j];
			if(a[j]<minx) minx=a[j];
		}
		cout<<minx<<" ";
		f[++l]=maxn;
	}
	cout<<endl;
	for(int i=1;i<=l;i++) cout<<f[i]<<" ";
}

然而理想是美好的,但是現實卻是可怕的:
數據范圍
\(50%\)的數據,\(n<=10^5\)

\(100%\)的數據,\(n<=10^6\)
對於這組數據,我們\(O(n*k)\)的方法只能拿\(70\)分(也許是我太蒟了) 所以我們要引進一種新的算法:單調隊列
讓我們先來理解一下單調隊列的工作情況:
對於一個有框的隊列中,我們每次讀入一個數,
\(For\) \(example\)
有這么一組數:
\(1\) \(2\) \(3\) \(2\) \(5\) \(6\) \(7\) \(8\) \(9\) \(10\)
\(k=3\)
我們先讀入\(1\),因為沒有隊首,就將他加入隊。
接着,讀入\(2\),\(2\)\(1\)大且\(2\)\(1\)之后\(,\)所以,我們將\(2\)入隊,\(1\)出隊
同理,我們接着讀入\(3\)
可是,到了\(2\)之后,我們發現,\(2\)<\(3\),但是,\(2\)\(3\)之后出現,所以,我們將\(2\)入隊,所以\(,\)單調隊列中變成了\(3\) \(2\)
接着,我們讀入\(4\),因為\(4>3>2\),所以,我們將\(2\)\(3\)出隊,隊列中只留下\(4\)
同理,我們后面就是不斷地出隊,入隊的情況。
那有人可能會說,那\(2\)入隊有什么用?
那我們再來看一組數據:
\(1\) \(2\) \(3\) \(2\) \(1\):\(k=2\)
這組數據,前面都與之前一樣,
\(1\)入隊時,情況就發生了變化:
此時\(3\)由於\(k=2\)出隊
那么,隊首就變成了\(2\),那我們就輸出\(2\),
所以這就是隊首\(>\)讀入的數,但仍要入隊的原因
我們每讀入一個數,就將他與隊首比較\(:\)
如果隊首比他小,那么隊中比這個數小的都出隊(這個數的可持久性比隊中的強且這個數比隊首大)
如果這個數比隊首小,將他入隊:
如果老的數超出范圍,將他出隊:
最終,隊中留下的就是最大的或最年輕的,代碼\(:\)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 2000003
#define f(i,a,b) for(int i=a;i<=b;i++)
int a[maxn];
struct s{
    int id;
    int num;
};
s f[maxn];
int l=1;
int r;
int n,m;
int read() {
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {
        if(ch=='-') f=-f;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void write(int x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int main() {
    n=read();m=read();
    for(int i=1;i<=n;i++) {
        a[i]=read();
    }
    int sum=0;
    f(i,1,n+1) {
        if(sum<m) sum++;
        else if(l<=r) {
            if(f[l].id+m<i) l++;
            write(f[l].num);
            putchar(' ');
        }
        while(f[r].num>=a[i]&&l<=r) r--;
        f[++r].num=a[i];
        f[r].id=i;
    }
    putchar('\n');
    l=1;r=0;sum=0;
    memset(f,0,sizeof(f));
    f(i,1,n+1) {
        if(sum<m) sum++;
        else if(l<=r) {
            if(f[l].id+m<i) l++;
            write(f[l].num);
            putchar(' ');
        }
        while(f[r].num<=a[i]&&l<=r) r--;
        f[++r].num=a[i];
        f[r].id=i;
    }
}

\(PS:read\)\(write\)可以忽略


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM