BZOJ 4540: [Hnoi2016]序列 莫隊算法


Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 1412 Solved: 663

Description

給定長度為n的序列:a1,a2,…,an,記為a[1:n]。類似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,…,ar-1,ar。若1≤l≤s≤t≤r≤n,則稱a[s:t]是a[l:r]的子序列。現在有q個詢問,每個詢問給定兩個數l和r,1≤l≤r≤n,求a[l:r]的不同子序列的最小值之和。例如,給定序列5,2,4,1,3,詢問給定的兩個數為1和3,那么a[1:3]有6個子序列a[1:1],a[2:2],a[3:3],a[1:2],a[2:3],a[1:3],這6個子序列的最小值之和為5+2+4+2+2+2=17。

Input

輸入文件的第一行包含兩個整數n和q,分別代表序列長度和詢問數。接下來一行,包含n個整數,以空格隔開,第i個整數為ai,即序列第i個元素的值。接下來q行,每行包含兩個整數l和r,代表一次詢問。

Output

  對於每次詢問,輸出一行,代表詢問的答案。

Sample Input

5 5

5 2 4 1 3

1 5

1 3

2 4

3 5

2 5

Sample Output

28

17

11

11

17

HINT

1 ≤N,Q ≤ 100000,|Ai| ≤ 10^9

Source


好頹廢啊,一上午就寫了這么一道題,真頹廢


一看是一道莫隊題,貌似很水的樣子,然后也就只看出了是道莫隊題,然后就不會轉移了


於是看了題解%%%

我再敘述一遍,順帶復習

首先我們發現如果對 [l,r] 轉移到 [l1,r] 或者 [l,r+1] 貌似只會增加 rl+1 個區間來計算答案

那么我們如果每次都去 for 一遍區間,顯然非常不優,那么怎么辦呢

我們用 lefti 表示以 i 作為最小值可以到的最左邊區間的端點,換句話說, i 的左邊第一個小於其的數的位置

那么如果令 dp[i][j] 表示從 i 這個位置一直往左走 j 位的以 i 為右端點的所有區間的答案

那么顯然有 dp[i][j]=dp[lefti][j]+(ilefti)a[i]

就是說一直到 lefti+1 ,即 [lefti+1,j] 這個區間的所有以 j 結尾的區間的最小值都是 a[i]

那么怎么計算 dp[lefti][j] 呢,顯然我們可以計算下一個比 lefti 位置的值更小的值,比如這個位置是 loc

那么區間 [loc+1,lefti] ,即所有左端點在這個區間里的,右端點為 j 的區間的答案都是 a[lefti]

這段區間的貢獻是 (leftiloc)a[lefti]

於是我們一直跳,一直到找到左端點為 i 為止

這就相當於一棵樹的結構,我們從右邊的一個較小值的位置連到他左邊的第一個比他更小的值

邊權為區間長度乘以右邊的那個較小值

於是我們維護一個 up 數組和 down 數組分別表示這棵”樹”中,這個點到這棵”樹的根”的邊權和

就好像我們要求樹上兩個點的距離,兩個點中淺的一個點在深的一個點到根的路徑上時

維護兩個點到根節點的距離然后相減一樣,所以這是有區間相減的性質的

所以我們維護一個 st 表,查詢區間最小值,查詢到區間最小值之后去求這個最小值到我們要求的位置的值

long long call(int l,int r){//考慮左端點對l到r區間的影響 
int loc=find(l,r);//find為找區間l,r里最小的那個值的位置(在原來的數列中)
return A[loc]*(r-loc+1)+up[l]-up[loc];
}

如上所示是一個求 l 對右端點在 [l,r] 里的所有區間對答案的貢獻的函數,我們是先求得一個最小值

然后再用這個最小值去求右邊區間的貢獻和左邊的 O(1) 查詢貢獻

然而為什么我們需要一個 st 表呢?

注意到我們的 r 這個位置

他是否一定是從 l 這個位置一直往右連邊所能連到的點呢(即不斷往右找第一個比其小的數)

好像不一定吧 233 ,所以說,如果沒有連上的話,顯然是不能直接計算的啦

然而我們還是可以直接找一下 [l,r] 區間內的最小值,然后去從這個最小值轉移

因為從左端點到右端點的最小值都已經找到了,那么從左端點往右連的邊中,不經過這個最小值說不過去了吧

最后一個問題: up 數組和 down 數組怎么求?我們可以用單調棧來求,這里以求 down 數組為例

因為 down 數組是求以i為結尾的一直到”樹根”的答案,所以我們每次就先把單調棧里的數 pop

pop 的原則是單調棧里的元素大於當前的元素,直到小於當前的元素或者 pop

此時單調棧里存的也就是第一個小於當前位置的數的位置了,處理完之后把新的數 push 進去

由於新的數更新,所以之后的數如果找到這個數的話,那么一定是最近的比其小的數

(好像這幾句話都是很基礎的東西 233 ,是因為我菜啦)

int top=0;
for(register int i=1;i<=n;i++){
while(top&&A[i]<=A[sta[top]])top--;//單增的棧,處理以i結尾
down[i]=down[sta[top]]+A[i]*(i-sta[top]);//棧內存的是一個位置,第一個比當前位置小的位置,而這個位置是一定已經被處理過的
sta[++top]=i;//down存的是以i結尾的一直到根的值
}

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int MAXN=100000+10;
struct Q{int l,r,blo,id;long long ans;}q[MAXN];
int n,m,sta[MAXN],dp[MAXN<<1][20];
long long A[MAXN],down[MAXN],up[MAXN];
bool cmp(const Q &A,const Q &B){
if(A.blo!=B.blo) return A.blo<B.blo;
if(A.r!=B.r) return A.r<B.r;
return A.l<B.l;
}
void init(){
int top=0;
for(register int i=1;i<=n;i++){
while(top&&A[i]<=A[sta[top]])top--;
down[i]=down[sta[top]]+A[i]*(i-sta[top]);
sta[++top]=i;
}
top=0;sta[top]=n+1;
for(register int i=n;i;i--){
while(top&&A[i]<=A[sta[top]])top--;
up[i]=up[sta[top]]+A[i]*(sta[top]-i);
sta[++top]=i;
}
}
int mn(int x,int y){
if(A[x]<A[y]) return x;
else return y;
}
void get_st(){
A[0]=inf;
for(register int i=1;i<=n;i++) dp[i][0]=i;
for(register int j=1;(1<<j)<=n;j++)
for(register int i=1;i+(1<<j)-1<=n;i++)
dp[i][j]=mn(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
int find(int L,int R){
int M=0;while((1<<(M+1))<(R-L+1)) M++;
return mn(dp[L][M],dp[R-(1<<M)+1][M]);
}
long long call(int l,int r){
int loc=find(l,r);
return A[loc]*(r-loc+1)+up[l]-up[loc];
}
long long calr(int l,int r){
int loc=find(l,r);
return A[loc]*(loc-l+1)+down[r]-down[loc];
}
void Mos_Algorithm(){
sort(q+1,q+m+1,cmp);
int l=1,r=0;
long long tmp=0;
for(register int i=1;i<=m;i++){
while(r<q[i].r) tmp+=calr(l,++r);
while(r>q[i].r) tmp-=calr(l,r--);
while(l>q[i].l) tmp+=call(--l,r);
while(l<q[i].l) tmp-=call(l++,r);
q[q[i].id].ans=tmp;
}
for(register int i=1;i<=m;i++) printf("%lld\n",q[i].ans);
}
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) scanf("%lld",&A[i]);
init();get_st();
int block=sqrt(n);
for(register int i=1;i<=m;i++){scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;q[i].blo=(q[i].l-1)/block+1;}
Mos_Algorithm();
return 0;
}
/*
5 5
5 2 4 1 3
1 5
1 3
2 4
3 5
2 5
*/

這里寫圖片描述


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com