其實我是真的不想寫這個東西的,只不過作了一些這方面的水題,乾脆寫成一個專題git
真的是水題,不毒瘤的PJ難度水題。我真是太菜了算法
在維護一段區間的最值時,你通常會怎麼作?優化
\(O(1)\)查詢RMQ,或者是什麼都能搞的線段樹spa
若是隻須要求一次呢?code
仍是RMQ/線段樹隊列
若是數據範圍爲\(10^7\) or more?it
咱們就能夠用到玄學的單調棧/隊列了。io
首先相信棧和隊列你們都不陌生吧,那麼加了個單調上去意味着什麼呢?class
序列是單調的(廢話),說明咱們能夠很快的找出最值變量
好像有這麼一點道理,例如咱們要求出一段序列的最小值
咱們使一個隊列裏的元素單調遞減,具體的:
對於每個元素\(x\in q\),都有\(q_x>q_{x+1}\)
所以咱們直接把隊頭拿出來就行了啊。
那麼可能有人要問了,這TM不是直接開一個變量記錄一下最小值就好的事情嗎?
可是詢問區間你左端點仍是要向右彈出的啊!所以咱們都要保留一個對將來可能有用的解。
只有當一對\(x,y\)元素知足\(x>y\)且\(a_x<a_y\)時\(y\)的存在纔沒有任何有意義。
複雜度是比較卓越的\(O(n)\),通常起輔助做用,經常使用來優化DP等算法。
不過咱們今天不講這麼難的,咱們只講SB板子題。
題目大意:求一段長度爲\(k\)的區間內的最小/大值。
這個單調隊列板子題。上面已經講的比較詳細了,咱們維護兩個單調隊列便可。
CODE
#include<cstdio> #include<cctype> const int N=1e6+5; using namespace std; int n,k,x; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1; while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; } inline void write(int x) { if (x<0) putchar('-'),x=-x; if (x>9) write(x/10); putchar(x%10+'0'); } namespace min { int q[N],num[N],ans[N],head=1,tail,cnt; inline void push(int x,int id) { while (tail>=head&&q[tail]>x) --tail; q[++tail]=x; num[tail]=id; } inline void check(int now) { if (num[head]+k<=now) ++head; ans[++cnt]=q[head]; } }; namespace max { int q[N],num[N],ans[N],head=1,tail,cnt; inline void push(int x,int id) { while (tail>=head&&q[tail]<x) --tail; q[++tail]=x; num[tail]=id; } inline void check(int now) { if (num[head]+k<=now) ++head; ans[++cnt]=q[head]; } }; int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(k); for (i=1;i<=k;++i) read(x),min::push(x,i),max::push(x,i); min::check(k); max::check(k); for (i=k+1;i<=n;++i) read(x),min::push(x,i),max::push(x,i),min::check(i),max::check(i); for (i=1;i<=min::cnt;++i) write(min::ans[i]),putchar(i^min::cnt?' ':'\n'); for (i=1;i<=max::cnt;++i) write(max::ans[i]),putchar(i^max::cnt?' ':'\n'); return 0; }
比較簡單的套路題,拿來練一下RMQ也是挺好的。
咱們對於每個點,處理出它兩端大於它的第一個位置的數
這樣咱們就能夠算出當\(i\in [l,r],a_i=min(l,r)\)時每個數的貢獻。
這樣保證不會遺漏。因爲\(a_i>0\)。所以取的數越多越好
CODE
#include<cstdio> #include<cctype> const int N=1e5+5; using namespace std; int n,k,x,a[N],stack[N],front[N],back[N],top,num[N]; long long sum[N],ans; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; while (!isdigit(ch=tc())); while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); } inline long long max(long long a,long long b) { return a>b?a:b; } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); for (i=1;i<=n;++i) read(a[i]),sum[i]=sum[i-1]+a[i]; for (i=1;i<=n;++i) { while (top&&stack[top]>=a[i]) --top; front[i]=num[top]; stack[++top]=a[i]; num[top]=i; } for (top=0,num[0]=n+1,i=n;i>=1;--i) { while (top&&stack[top]>=a[i]) --top; back[i]=num[top]; stack[++top]=a[i]; num[top]=i; } for (i=1;i<=n;++i) ans=max(ans,1LL*a[i]*(sum[back[i]-1]-sum[front[i]])); return printf("%lld",ans),0; }
又是一道SB題,咱們先將序列展開,方便操做。
即\(i\in[n+1,2n-1],a_i=a_{i-n}\),而後計算出前綴和
考慮每一次倒敘,就至關於求出一段長爲\(n\)的區間的前綴和的最小值,並判斷是否小於\(0\)
而後區間大小都固定了,上單調隊列不是很爽嗎?
CODE
#include<cstdio> #include<cctype> const int N=1e6+5; using namespace std; int n,k,x,a[N],q[N<<1],head=1,tail,num[N<<1],sum[N<<1],ans; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1; while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; } inline void push(int x,int id) { while (tail>=head&&q[tail]>x) --tail; q[++tail]=x; num[tail]=id; } inline int check(int now) { if (num[head]>now) ++head; return q[head]; } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); for (i=1;i<=n;++i) read(a[i]),sum[i]=sum[i-1]+a[i]; for (i=n+1;i<(n<<1);++i) sum[i]=sum[i-1]+a[i-n]; for (i=(n<<1)-1;i>=n;--i) push(sum[i],i); for (i=n-1;i>=0;--i) { if (check(i+n)>=sum[i]) ++ans; push(sum[i],i); } return printf("%d",ans),0; }