單調佇列入門——POJ
單調佇列,顧名思義,維護的佇列中的資料是呈單調遞增或者單調遞減的,在維護單調佇列時,例如單調遞增佇列,當有新的資料入隊,則從隊尾開始,把所有大於該數的專案pop出佇列,保證了這個資料入隊後整個佇列依舊是單調遞增的,一般單調佇列的實現是用一個定長陣列和變數head和tail來維護隊頭和隊尾。單調佇列一個簡單應用是定長區間的最大/最小值問題,又叫做視窗滑動,就是一個長度為k的視窗在數列上滑動,每滑動一格就要求出當前視窗內的最大值/最小值。對於這種題目,我們需要做的就是維護單調佇列,並且即時把隊頭元素所處數列位置pos小於當前視窗左端的值彈出佇列,這樣每次維護完後,單調佇列的隊頭就是當前視窗區間的最大值/最小值了。因此還需要記錄佇列中每個元素的位置。
單調佇列的應用還是很多的,可以對一些演算法進行改進已優化複雜度,例如單調佇列揹包dp,不過我都還不會= =。
單調佇列很多時候做的事跟RMQ很像,不過RMQ可以查詢不定長區間的最大最小值,不過單調佇列的複雜度更低。
詳情看題來學習吧。
1.Sliding Window POJ - 2823
應該是最經典的單調佇列的應用了,方法我上面說過了。
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<queue>
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&&head1<tail1){ head1++; }
while (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&&head2<tail2){ head2++; }
while (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<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<queue>
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&&head2<tail2&&num[que1[head1]] - num[que2[head2]]>k){
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<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<string>
#include<cmath>
#include<set>
#include<map>
#include<queue>
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 (head<tail&&a[i].num - b[head].num>k){ head++; }
if (a[i].num <= k){ ans = max(ans, sum[a[i].pos]/p); continue; }
if (b[head].pos<a[i].pos&&head<tail){ ans = max(ans, (sum[a[i].pos] - sum[b[head].pos])/p); }
}
printf("Case %d: %lld\n", cas, ans);
}
return 0;
}
剛學習這個,理解還不是很深刻, 題目有點變形就反應不過來了,講解也講不了很清楚,有待深入學習。
相關推薦
單調佇列入門——POJ
單調佇列,顧名思義,維護的佇列中的資料是呈單調遞增或者單調遞減的,在維護單調佇列時,例如單調遞增佇列,當有新的資料入隊,則從隊尾開始,把所有大於該數的專案pop出佇列,保證了這個資料入隊後整個佇列依舊是單調遞增的,一般單調佇列的實現是用一個定長陣列和變數he
【POJ 2823】Sliding Window 【滑動視窗/單調佇列入門
題目大意 輸入一個長度為n(n≤≤106106)的數列,給定一個長度為k的視窗,讓這個視窗在數列上移動,求移動到每個位置視窗中包含數的最大值和最小值。即設序列為A1,A2,…,AnA1,A2,…,AnA1,A2,…,An,設f(i)=minAi−k+1Ai−k+
poj 2823 單調佇列入門題(內含手寫佇列的學習和模板)
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 39011 Accepted: 11554 Case Time Limit: 5000MS Descripti
POJ 2823 單調佇列入門題
【解題報告】 這題是可以用優先佇列做的,這樣會簡單一些,不過考慮到是為了熟練的掌握斜率優化這一技巧,所以特意通過做這道題目來熟悉單調佇列的基本用法。因為考慮到單調佇列是從隊尾插入,訪問隊首元素。所以使用了雙端佇列這一STL。雖然要比手寫的模擬佇列要麻煩一些
poj~2823(單調佇列入門)
如果當初看單調佇列先從這題開始做的話,應該會快點理解吧。 題意就是求區間最值,這是單調佇列最簡單的應用。 題意: 給你n個數和區間長度k,求區間遍歷到第i個數時的最小和最大值,所以是n-k+1個值。
POJ 2823 Sliding Window(單調佇列入門題)
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 67218 Accepted: 19088 Case Time Limit: 5000MS Description An array of size
POJ2823——Sliding Window 單調佇列入門
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 40596 Accepted: 11992 Case Time Limit: 5000MS Description
POJ 2823 (從經典滑動視窗最大值問題入門單調佇列)
題目連結 題目大意 輸入一個長度為n(n≤106)的數列,給定一個長度為k的視窗,讓這個視窗在數列上移動,求移動到每個位置視窗中包含數的最大值和最小值。即設序列為A1,A2,…,An,設f(i)=min{Ai−k+1,Ai−k+2,…,Ak} ,g(
POJ 1276 多重揹包+多重揹包可行化問題+單調佇列優化
A Bank plans to install a machine for cash withdrawal. The machine is able to deliver appropriate @ bills for a requested cash amount. The machine use
單調棧,單調佇列的入門
轉載地址:https://www.cnblogs.com/tham/p/8038828.html 我自己的話單調棧是也用陣列模擬出來的,我是根據這個部落格學的,原理挺簡單的,但是做題思想的轉變有點麻煩,最後陣列模擬的單調佇列其實是雙向佇列。 下邊是大佬的部落格: 單調佇列是什麼呢?可以直
POJ - 2823 Sliding Window【單調佇列優化dp && c++快讀】
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 72718 &nb
Dividing the Path POJ - 2373 詳細題解 (線性DP + 單調佇列優化)
這道題目是一道較為複雜的線性Dp題目, 我會按照思路詳細列舉一些本題解題過程 按照動態規劃問題的一般解題思路來, 首先, 我們需要定義一個狀態dp[i] 表示恰好到覆蓋到 [0, i]時最少需要的噴水裝置數量, 答案自然也就是dp[L], 把長度作為容量, 是線性Dp的一種常見方案 然
poj 2823 Sliding Windows (單調佇列+輸入輸出掛)
Sliding Window Time Limit: 12000MS Memory Limit: 65536K Total Submissions: 73426
POJ 1821 Fence (單調佇列優化DP)
題目大意:有k個工人,有一排n個磚頭,現在要給磚頭染色,每個工人要麼不染色,要麼選擇一個包含$s_{i}$的,長度不大於$l_{i}$的區域進行染色,然後他們會獲得$len\cdot p_{i}$的報酬,求使所有工人總報酬最大的方案,輸出最大報酬 定義$f[i][j]$表示已經遍歷到了第i個工人,遍歷到了第
POJ-2823-Sliding Window (單調佇列)
原題連結: http://poj.org/problem?id=2823 An array of size n ≤ 10 6 is given to you. There is a sliding window of size k which is moving from the ver
[poj 1821] Fence {單調佇列優化dp}
題目 http://poj.org/problem?id=1821 解題思路 先把所有工匠按照 S i
單調佇列 POJ 2823 Sliding Window
Sliding Window An array of size n ≤ 10 6 is given to you. There is a sliding window of size kwhich is moving from
揹包問題入門(單調佇列優化多重揹包
揹包問題 寫這篇文章主要是為了幫幫新人吧,dalao勿噴.qwq 一般的揹包問題問法 每種物品都有一個價值w和體積c.//這個就是下面的變數名,請看清再往下看. 你現在有一個揹包容積為V,你想用一些物品裝揹包使得物品總價值最大. 01揹包 多種物品,每種物品只有一個.求能獲得的最大總價值. 我們考慮
POj 2823 單調佇列 / 優先佇列
解法1: 單調佇列 所謂單調佇列就是一個單調遞增或遞減的佇列,並不是什麼神奇的東西。 單調佇列(就遞增來說)嚴格來說就是隊頭的原素是最大的,不斷從隊尾加原素,如果隊尾的原素比要加入來的原素大,就不斷退隊,就到隊尾的原素比要加入來的原素小或等於要加入來的原素。 這題只要分別
單調佇列及其deque寫法 HDU 3415+Poj 4002 (日期處理) + 合併果子
嘗試用deque寫一下單調佇列,發現速度還是可以接受的,STL依賴症越來越嚴重了。。。。 HDU 3415 Max Sum of Max-K-sub-sequence 題意:給出一個有N個數字(-1000..1000,N<=10^5)的環狀序列,讓你求一個和最大的連續