天天,農夫 John 的N(1 <= N <= 50,000)頭牛老是按同一序列排隊. 有一天, John
決定讓一些牛們玩一場飛盤比賽. 他準備找一羣在對列中爲置連續的牛來進行比賽.
可是爲了不水平懸殊,牛的身高不該該相差太大.
John 準備了Q (1 <= Q <= 180,000) 個可能的牛的選擇和全部牛的身高 (1 <=
身高 <= 1,000,000). 他想知道每一組裏面最高和最低的牛的身高差異.
注意: 在最大數據上, 輸入和輸出將佔用大部分運行時間.
ST表是用來解決RMQ(區間最值)問題的算法python
預處理O(nlgn) 查詢O(1) 不支持在線查詢ios
最小值能夠合併但不支持分割算法
好比說咱們知道[1,9]和[6,10]的最小值,咱們能夠知道[1,10]的最小值,但不能知道[6,9]的最小值數組
咱們能夠枚舉以每一個節點爲起點通過k個節點的最值大數據
可是預處理是O(n2),這時候咱們想到了倍增,一種十分巧妙的思想
spa
它能夠在變化規則相同的狀況下加速狀態轉移,咱們每次把k擴大一倍blog
f[i,j]表示[i,i+2j-1]區間內的信息繼承
f[i,j]:=f[i,j-1]∪f[i+2j-1]string
代碼實現it
//初始化 bin[0]=1; for(int i=1;i<20;i++) bin[i]=bin[i-1]*2;//bin[i]表示2的i次方 Log[0]=-1; for(int i=1;i<=200000;i++) Log[i]=Log[i/2]+1;//Log[i]表示log(i) for(int i=1;i<=n;i++) mn[0][i]=a[i];//顯然i到i+2^0-1就i一個位置,那麼最小值等於本身自己的值 for(int i=1;i<=Log[n];i++) for(int j=1;j<=n;j++) if(j+bin[i]-1<=n) mn[i][j]=min(mn[i-1][j],mn[i-1][j+bin[i-1]]);//狀態繼承 //查詢x到y的最小值 int t=Log[y-x+1]; printf("%d\n",min(mn[t][x],mn[t][y-bin[t]+1]));
[例題1]Balanced Lineup
天天,農夫 John 的N(1 <= N <= 50,000)頭牛老是按同一序列排隊. 有一天, John
決定讓一些牛們玩一場飛盤比賽. 他準備找一羣在對列中爲置連續的牛來進行比賽.
可是爲了不水平懸殊,牛的身高不該該相差太大.
John 準備了Q (1 <= Q <= 180,000) 個可能的牛的選擇和全部牛的身高 (1 <=
身高 <= 1,000,000). 他想知道每一組裏面最高和最低的牛的身高差異.
注意: 在最大數據上, 輸入和輸出將佔用大部分運行時間.
6
3
0
[參考程序]
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<cmath> #include<algorithm> using namespace std; const int N = 50005; int FMAX[N][20], FMIN[N][20]; void RMQ(int n) { for(int j = 1; j != 20; ++j) { for(int i = 1; i <= n; ++i) { if(i + (1 << j) - 1 <= n) { FMAX[i][j] = max(FMAX[i][j - 1], FMAX[i + (1 << (j - 1))][j - 1]); FMIN[i][j] = min(FMIN[i][j - 1], FMIN[i + (1 << (j - 1))][j - 1]); } } } } int main() { int num, query; int a, b; while(scanf("%d %d", &num, &query) != EOF) { for(int i = 1; i <= num; ++i) { scanf("%d", &FMAX[i][0]); FMIN[i][0] = FMAX[i][0]; } RMQ(num); while(query--) { scanf("%d%d", &a, &b); int k = (int)(log(b - a + 1.0) / log(2.0)); int maxsum = max(FMAX[a][k], FMAX[b - (1 << k) + 1][k]); int minsum = min(FMIN[a][k], FMIN[b - (1 << k) + 1][k]); printf("%d\n", maxsum - minsum); } } return 0; }
[例題2] 不同凡響
【題目描述】
A是某公司的CEO,每月都會有員工把公司的盈利數據送給A,A是個不同凡響的怪人,A不注重盈利仍是虧本,而是喜歡研究「完美序列」:連續的互不相同的序列。A想知道區間[L,R]之間最長的完美序列。
【輸入格式】
第一行兩個整數N,M(1<=N,M<=200000),N表示連續N個月,編號爲0到N-1,M表示詢問的次數。第二行N個整數(絕對值不超過106),第i個數表示該公司第i個月的盈利值。接下來M行每行兩個整數L,R(0<=L<=R<=N-1),表示A詢問的區間。
【輸出格式】
輸出M行,每行一個整數對應詢問區間內的完美序列的最長長度。
【樣例輸入】
9 2
2 5 4 1 2 3 6 2 4
0 8
2 6
4 8
【樣例輸出】
6
5
4
[分析]
本題是RMQ較複雜的問題。用last[value]表示盈利值value最近出現的位置,一開始所有賦爲0。
用st[i]表示以第i個數爲結尾的最長完美序列的起始位置,st[i]=max(st[i-1],last[a[i]]+1)(a[i]表示第i個月的盈利值),用f[i]表示第i個數爲結尾的最長完美序列的長度,顯然f[i]=i-st[i]+1
for i:=1 to n do begin read(a[i]); st[i]:=max(st[i-1],last[a[i]]+1); q[i]:=i-st[i]+1; last[a[i]]:=i; end;
能夠發現st數組單調不減。
因而對於一個分割點mm有兩種狀況:
一、mm左邊一部分st[]值<=l-1
二、mm右邊一部分st[]值>=l
由於st[]是單調的,因此能夠用二分法。
左半部分最大值即mm-l,右半部分最大值即q[mm..r]的最大值
uses math; var a,st,q:array[0..200001]of longint; last:array[-1000001..1000001]of longint; rmq:array[0..200001,0..21]of longint; i,j,ans,n,m,l,r,mm,len:longint; function search(x,y:longint):longint; var ll,rr,mid:longint; begin if st[x]=x then exit(x); if st[y]<x then exit(y+1); ll:=x;rr:=y; while ll<=rr do begin mid:=(ll+rr) div 2; if st[mid]<x then ll:=mid+1 else rr:=mid-1; end; exit(ll); end; begin readln(n,m); for i:=1 to n do begin read(a[i]); st[i]:=max(st[i-1],last[a[i]]+1); q[i]:=i-st[i]+1; last[a[i]]:=i; end; for i:=1 to n do rmq[i,0]:=q[i]; for j:=1 to trunc(ln(n)/ln(2)) do for i:=1 to n-(1 shl j)+1 do rmq[i,j]:=max(rmq[i,j-1],rmq[i+(1 shl (j-1)),j-1]); for i:=1 to m do begin readln(l,r); mm:=search(l,r); if mm>l then ans:=mm-l; if mm<=r then begin len:=trunc(ln(r-mm+1)/ln(2)); ans:=max(ans,max(rmq[mm,len],rmq[r-(1 shl len)+1,len])); end; writeln(ans); end; end.