1. 程式人生 > 其它 >【題解】CF601B Lipshitz Sequence

【題解】CF601B Lipshitz Sequence

這種題第一眼看上去不可做,我們考慮證明一個結論:

對於一個區間的 \(Lipschitz\) 常數 \(k\) ,應滿足:

\[k=\max_{i=l}^{r-1} \left\{\frac{a[i+1]-a[i]}{i+1-i}\right\} \]

\[k=\max_{i=l}^{r-1} \left\{|a[i+1]-a[i]|\right\} \]

證明:

考慮三個數的情況,設它們分別是:\(\left\{0,x,\infty\right\}\)

那麼,相鄰的區間的 \(Lipschitz\) 常數 \(k=\left\{x,\infty-x\right\}\)

而整體區間的 \(Lipschitz\)

常數 \(k=\frac{\infty}{2}\)

那麼考慮 \(x\) 的取值會發現,無論 \(x\) 怎麼取值,相鄰區間的 \(Lipschitz\) 常數 \(k\) 必然要大於整體區間的 \(k.\) 而根據三個數的區間進行推廣即可。

證畢。

那麼我們接下來的任務就是回答詢問了,觀察到詢問數目很少,所以我們用 \(O(nq)\) 的複雜度就可以了。

考慮如何 \(O(n)\) 回答一組詢問:

觀察相鄰區間的 \(k\) 會出現多少次。我們求出左邊和右邊第一個大於它的數的位置,那麼在這個區間中,任意一個包含它的區間都會讓 \(k\) 出現一次。那麼如果我們可以 \(O(n)\) 求出這個東西,問題就解決了。

顯然單調棧可以完成這個任務。

於是這題做完了。單調棧內部維護數值遞減的序列下標,每次彈出棧的時候更新棧頂元素的目標值就好了。

總複雜度:\(O(nq).\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=2e5+10;
const int inf=(1<<30);
int n,q,a[MAXN],h[MAXN];
inline int Abs(int x){if(x<0)return -x;return x;}
int st[MAXN],top;
int l[MAXN],r[MAXN];
signed main(){
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;++i)scanf("%lld",&h[i]);
	for(int i=1;i<n;++i)a[i]=Abs(h[i+1]-h[i]);
	for(;q;q--){
		int L,R;
		scanf("%lld%lld",&L,&R);
		R--;
		memset(l,0,sizeof l);
		memset(r,0,sizeof r);
		int ans=0;
		for(int i=L,j=1;i<=R;++i,++j)a[j]=Abs(h[i+1]-h[i]);
		int len=R-L+1;
		top=0;
		for(int i=1;i<=len;++i){
			while(top&&a[st[top]]<a[i]){
				r[st[top]]=i-st[top];
				top--;
			}
			l[i]=i-st[top];
			st[++top]=i;
		}
		while(top)r[st[top]]=len+1-st[top],top--;
		for(int i=1;i<=len;++i)ans+=a[i]*l[i]*r[i];
		printf("%lld\n",ans);
	}
	return 0;
}