[Codeforces601B]Lipshitz Sequence

题意

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

\[\operatorname{L}(h)=\begin{cases} 0&n=1\\ \max\limits_{1\le j< i\le n} {\left\lceil\dfrac{|h[j]-h[i]|}{j-i}\right\rceil}&n\ge 2 \end{cases}\]

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

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

\[\sum\limits_{l\le i\le j\le r} \operatorname{L}(h[i..j])\]

题解

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

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

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


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

  • 命题:数列 \(a[x..y]\)Lipschitz 常数必定是由数列中 相邻的两项 计算而来。即 \(\operatorname{L}(a[x..y])=\max\limits_{x\le i<y}\left\lceil\dfrac{|a_i-a_{i+1}|}{2}\right\rceil\)

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

不妨将 \((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}\) 的情况:

3V38W4.png

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

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

3V8U3Q.png

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

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

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

于是我们可以很自然地转化 \(\operatorname{L}(h[l..r])\) 为:\(\max\limits_{l\le i\le r} \left\lceil\dfrac{|h_{i+1}-h_i|}{2}\right\rceil\)

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

于是原问题就成了求:

\[\sum\limits_{l\le i<j<r}\operatorname{L}(h[i..j])=\sum\limits_{l\le i<j< r}\max\limits_{i\le k< j} \left\lceil\dfrac{b_k}{2}\right\rceil\]


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

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

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

上代码!

#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\times q)\)

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


后记

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

求赞 QwQ

猜你喜欢

转载自www.cnblogs.com/-Wallace-/p/12337827.html
今日推荐