演算法入門 單調佇列

2022-06-01 18:36:09 字數 3512 閱讀 1153

現在,kuaid有一台電腦,他要完成乙個任務。他拿到了乙個只有10個數字的序列和兩個數字\(x,y\),數列會完整的顯示在電腦螢幕上,他要找出區間\([x,y]\)之間的最小值,由於kuaid懶得很,他決定寫**解決這個問題

kuaid沉思了一會,決定簡單(粗暴)地解決這個問題,讀入整個序列,從\(x\)到\(y\)一一遍歷,隨時更新就行了,於是他寫出了下面的**:

#include #include #define max 0x3ffffff

using namespace std;

int a[11],x,y,minn = max;

int main()

kuaid很輕易的解決了問題,但是他還有很充足的時間,他開始了胡思亂想。

「我又沒有更能裝13,或者更麻煩(?)的解決方法呢?」

他拿出了乙個資料,開始嘗試用乙個佇列解決,數列為:\(7,6,8,12,9,10,3,1,5,6\);\(x=3,y=7\);

考慮每乙個數時,先判斷是否在範圍之內,若在範圍中,將它與隊中元素比較,隊中比這個數大的都出隊,這個數由於出現晚,且必在範圍內,將他入隊(此時kuaid覺得這樣更加麻煩了,但他還是想分析下去)。

考慮第1個數:7,但7並不在要求的範圍內,忽略,此時隊列為空

考慮第2個數:6,但6並不在要求的範圍內,忽略,此時隊列為空

考慮第3個數:8,此時8在範圍內,且隊中為空,於是將8入隊,此時隊列為\(\\)

考慮第4個數:12,佇列中8比12小,但12出現的晚,於是將12入隊,此時隊列為\(\\)

考慮第5個數:9,佇列中12比9大,於是將12出隊,9入隊,此時隊列為\(\\)

考慮第6個數:10,佇列中8,9比10小,但10出現的晚,於是將10入隊,此時隊列為\(\\)

考慮第7個數:3,佇列中8,9,10都比3大,於是將8,9,10出隊,3入隊,此時隊列為\(\\)

後面的數超出了範圍,必定都不入隊,kuaid發現,佇列中總是乙個單調上公升佇列,且隊首元素即為當前範圍內的最小值,故答案為3,kuaid停止了思考,寫出了以下**:

#include #include using namespace std;

int a[11],x,y,q[11];

int main()

cout << q[head];

return 0;

}

kuaid剛打完**,新的任務就到了,他拿到了一張,上有乙個長度為\(n\)的數列,由於顯示比例問題,kuaid的電腦螢幕只能同時顯示\(k\)個數,不知為何,並沒有給出讓kuaid求什麼。

kuaid想了想,看時間還充沛,決定自己給自己出乙個任務,當他從左向右檢視時,每一時刻向後檢視乙個數,此時螢幕最左邊的數會被隱藏,他想求出每一時刻螢幕上的最大值和最小值(kuaid有一些特殊的手段能瞬間從中得到所需的資訊)

kuaid並不想耗費太多的腦力,所以他決定使用暴力的方法,對狀態進行列舉;

片刻之後,他得到了以下**:

#includeusing namespace std;

int n,k;

int a[3000003];

int f[3000003];

int main()

cout << minx << " ";

f[++ tot] = maxn;

} cout << endl;

for(int i = 1;i <= tot;i ++)

cout << f[i] << " ";

return 0;

}

kuaid用上面的**完成了幾個資料的處理,可是他發現,這樣的時間複雜度為\(o(n \cdot k)\),在處理大資料時會花費掉他很多時間,甚至耽擱他過會兒的行程,kuaid可不想打亂自己看學習資源的計畫。kuaid開始思考優化。

kuaid突然想到了自己無聊時的想法,能不能用佇列進行優化?

在求最小值時,考慮每乙個數,將它與隊中元素比較,隊中比這個數大的都出隊(在當前範圍內,比這個數大的一定不是該範圍的最小值),這個數由於出現晚,即使比現在隊伍中的數大,但會在螢幕上持續出現更久(這個數會出現在後面的範圍),所以必須將他入隊,若當前隊首已出螢幕可檢視的範圍,將其出隊,那麼這個佇列一定是單調遞增的,這樣得到的隊首,不正是該區間的最小值嗎?

求最大值同理

如何判斷當前隊首是否超出範圍呢?每個數有乙個編號,為\(1,2,...,n\),當前考慮的是第\(i\)個數,當第\(i\)個數進入螢幕後,最左側為第\(i-k+1\)個數,也就是說,如果當前隊首的編號小於等於\(i-k\),這個數必定是超出範圍的。

於是,他得到了以下**:

#include #include #define max 2000005

using namespace std;

struct num;

int a[max]; //原數列

num q[max]; //佇列

int main()

} cout << endl;

front = 1;

back = 0; //記得需要清空佇列

for (int i = 1;i <= n;i ++)

} return 0;//完美結束

}

通過計算發現這樣的時間複雜度竟然只有\(o(n)\)!

kuaid定睛一看,這不就是單調佇列嗎!

以這個資料為例:\(n=8,k=3\)

\(a=\\)

則有下列**:

思考求最小值時,為什麼佇列中的數是單調遞增的。我們發現,當新考慮乙個數時,佇列中比它大的數都會出隊,因而在這個數之前的佇列中的所有數都比它小,由於每次最多只有乙個數出界,也就只用判斷一次隊首元素編號

如上面所說,在求最小值時,考慮每乙個數,將它與隊中元素比較,隊中比這個數大的都出隊(在當前範圍內,比這個數大的一定不是該範圍的最小值),這個數由於出現晚,即使比現在隊伍中的數大,但會在螢幕上持續出現更久(這個數會出現在後面的範圍),所以必須將他入隊,若當前隊首已出螢幕可檢視的範圍,將其出隊,那麼這個佇列一定是單調遞增的,這樣得到的隊首,正是該區間的最小值

單調佇列的使用並不廣泛,但對於某些題目有特別的效果

以上是本人無知時提出的暴論。。。單調佇列對某些 dp 具有很好的優化效果

歡迎到以下位址支援作者!

github:戳這裡

bilibili:戳這裡

luogu:戳這裡

單調棧,單調佇列的入門

我自己的話單調棧是也用陣列模擬出來的,我是根據這個部落格學的,原理挺簡單的,但是做題思想的轉變有點麻煩,最後陣列模擬的單調佇列其實是雙向佇列。下邊是大佬的部落格 單調佇列是什麼呢?可以直接從問題開始來展開。poj 2823 給定乙個數列,從左至右輸出每個長度為m的數列段內的最小數和最大數。數列長度 ...

單調佇列 入門

今天寫了人生中第乙個單調佇列,激動ing 先看一道單調佇列的入門題 乙個含有n項的數列 n 2000000 求出每一項前面的第m個數到它這個區間內的最小值。先寫出動規方程 f i min j合法 很明顯的,這是乙個n 2的動規,但是,我們可以注意到,數列中有些數無論如何都不會被選到.如 1 2 8 ...

單調佇列入門

單調佇列是一種佇列 廢話 其中佇列的元素保證是單調遞增或者是單調遞減的 那麼隊首的元素不就是最小 或最大 的嗎?我們結合具體的題目來看看吧 傳送門 p1886 滑動視窗 現在有一堆數字共n個數字 n 10 6 以及乙個大小為k 的視窗。現在這個從左邊開始向右滑動,每次滑動乙個單 位,求出每次滑動後視...