「Codeforces 601B」 Lipshitz Sequence

Description

定义数列 (h[1..n])Lipschitz 常数为:

[operatorname{L}(h)=egin{cases} 0&n=1\ maxlimits_{1le j< ile n} {leftlceildfrac{|h[j]-h[i]|}{j-i} ight ceil}&nge 2 end{cases}]

现给定一个长度为 (n) 的数列 (h[1..n]) 以及 (q) 个询问。

每个询问给出一个二元组 ((l,r)) ,要求对于每组询问求出所有子序列的 Lipschitz 常数之和,即求 :

[sumlimits_{lle ile jle r} operatorname{L}(h[i..j]) ]

Solution

请务必耐心看完切理解题面。

显然不是暴力可以随随便便搞出来的。

当然你也可以使用 ST 表或者线段树,不过这里介绍的是一种最简单的方法,效率也丝毫不亚于上面二者的方法——单调栈。


首先我们有一个命题,这是我们解题的关键。

  • 命题:数列 (a[x..y])Lipschitz 常数必定是由数列中 相邻的两项 计算而来。即 (operatorname{L}(a[x..y])=maxlimits_{xle i<y}leftlceildfrac{|a_i-a_{i+1}|}{2} ight ceil)

接下来给出证明(简单的解释而已,可能不太漂亮):

不妨将 ((i,a[i])) 视作平面上的一点,这样我们得到 (operatorname{size}(a)) 个点,从而将原命题转化为了这样一个新命题:在上述所有的点中任选两点构成的直线斜率的绝对值之中,最大的必定是由横坐标之差为 (1) 的两点(即相邻的)构成。

这个命题相比之前一个要好证很多。我们取这 (operatorname{size}(a)) 个点中任意三个,令其为 (A,B,C(x_A<x_B<x_C,y_A<y_B<y_C))。(这里只是一种情况,其他情况同理)。

  1. (k_{AB}<k_{BC}) 的情况:

「Codeforces 601B」 Lipshitz Sequence

图中可见, (k_{BC}>k_{AC})

  1. (k_{AB}>k_{BC})

「Codeforces 601B」 Lipshitz Sequence

(从左至右分别为点 (A,B,C)

图中可见, (k_{AB}>k_{AC})

再将其他情况手玩一下,可以发现,最大的斜率一定为相邻的两点所构成的直线的斜率。

于是我们可以很自然地转化 (operatorname{L}(h[l..r])) 为:(maxlimits_{lle ile r} leftlceildfrac{|h_{i+1}-h_i|}{2} ight ceil)

(b_{i}=|h_{i+1}-h_{i}|)

于是原问题就成了求:

[sumlimits_{lle i<j<r}operatorname{L}(h[i..j])=sumlimits_{lle i<j< r}maxlimits_{ile k< j} leftlceildfrac{b_k}{2} ight ceil ]

行了,经过以上的 瞎扯 ,问题已经原型毕露了。

可以看出这实质上就是一个 一段区间的所有子区间最大值之和 的问题。

那么直接用单调栈正着做一次,反着做一次,得到以任意一位为最小值的最大区间的边界,然后用乘法原理计算出对答案的贡献即可。

复杂度为 (O(n))

上代码!

Code

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : Codeforces 601B Lipshitz Sequence
 */
#include<iostream>
#include<stack>
#include<cmath>
using namespace std;

const int N=1e5+5;
int a[N],b[N];
int n,q;

#define left LEFT
#define right RIGHT
int left[N],right[N];
long long query(int l,int r)
{
	if(l>=r) return 0ll;
	int len=r-l;
	for(register int i=l;i<r;i++)
		b[i-l+1]=abs(a[i+1]-a[i]);
	stack<int> st;
	for(register int i=1;i<=len;i++)
	{
		while(!st.empty()&&b[st.top()]<=b[i]) st.pop();
		if(!st.empty()) left[i]=st.top();
		else left[i]=0;
		st.push(i);
	}
	st=stack<int>();
	for(register int i=len;i>=1;i--)
	{
		while(!st.empty()&&b[st.top()]<b[i]) st.pop();
		if(!st.empty()) right[i]=st.top();
		else right[i]=len+1;
		st.push(i);
	}
	long long ret=0ll;
	for(register int i=1;i<=len;i++)
		ret+=b[i]*1ll*(i-left[i])*(right[i]-i);
	return ret;
}

signed main()
{
	cin>>n>>q;
	for(register int i=1;i<=n;i++)
		cin>>a[i];
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		cout<<query(l,r)<<endl;
	}
	return 0;
}

复杂度 (O(n imes q))

虽然看着不小,但已经足以通过此题了。


Notes

画图工具:https://www.desmos.com/calculator