【题解】Diferenc-Diferencija [SP10622]

【题解】Diferenc-Diferencija [SP10622]

传送门:\(\text{Diferenc-Diferencija}\) \(\text{[SP10622]}\)

【题目描述】

序列的值被定义成其中最大的元素减去最小的元素。如序列 \((3,1,7,2)\) 的值为 \(7-1=6\), 序列 \((42,42)\) 的值为 \(42-42=0\)

现给定一长为 \(n\) 的序列 \(a\),求出所有连续子序列的值的和。

【样例】

样例输入:
3
1
2
3
样例输出:
4

样例输入:
4
7
5
7
5
样例输出:
12

样例输入:
4
3
1
7
2
样例输出:
31

【数据范围】

\(100 \%:\) \(2 \leqslant n \leqslant 3*10^5,\) \(1 \leqslant a[i] \leqslant 10^8\)


【分析】

先将子区间的右端点固定为 \(r\),此时一共有 \(r\) 个左端点 \((l \in [1,r])\) 可与之组成连续子序列,用 \(f_1[l]\) 表示 \(max \{a[j]\}(j \in [l,r])\)\(f_2[l]\) 表示 \(min \{a[j]\}(j \in [l,r])\) 。于是以 \(i\) 为右端点 \(r\) 的子区间贡献和为 \(\sum_{l=1}^{r} (f_1[l]-f_2[l])\),即 \(\sum_{l=1}^{r} f_1[l] - \sum_{l=1}^{r} f_2[l]\) 。我们可以分开算 \(f_1,f_2\) 的总和。

当右端点移至 \(r+1\) 时,需要用 \(a[r+1]\) 来更新 \(f_1,f_2\),可以直接扫描 \([1,r]\),但时间不过不了关。

随着 \(l\) 的减小,\(f_1[l]=max(a[l],f_1[l+1])\),可以发现 \(f_1[l]\) 是单调不下降的,同理 \(f_2[l]\) 单调不上升。

随着 \(r\) 的增大,\(f_1[l]=max(f_1[l],a[r+1])\),可以发现 \(f_1[l]\) 仍是单调不下降的,同理 \(f_2[l]\) 单调不上升。

然后我们就会发现一个现象:每次新加进来一个数 \(a[r+1]\) 时,它会将以 \(r+1\) 结尾的一段连续的区间 \(f_1[l],f_2[l](l \in [?,r+1])\) 全部赋值为 \(a[r+1]\),而且被覆盖掉的原数对这之后的区间不再有任何贡献。

于是我们可以用两个单调栈分别维护 \(f_1,f_2\),由于下标也是单调递增,所以可以将 \(f\) 值相同的合并起来,用 \(g\) 记录 \(f\) 相同的下标个数,另设一个变量 \(S\) 表示以 \(i\) 为右端点的贡献和,当加入新的 \(a[r]\) 时就不断弹走队尾直至保持单调时结束,同时更新 \(S\),最后累加答案即可。

【Code】

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=3e5+2;
int n,t1,t2,a[N],f1[N],f2[N],g1[N],g2[N];LL S,ans;
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
int main(){
    in(n);
    for(Re i=1;i<=n;++i)in(a[i]);
    f1[++t1]=a[1],f2[++t2]=a[1],g1[t1]=g2[t2]=1,ans=S=0;//初始化入队
    for(Re i=2;i<=n;++i){
        Re tmp=1;//f1[i]和f2[i]都一定会被覆盖,所以初始化为1
        while(t1&&f1[t1]<=a[i])S-=(LL)f1[t1]*g1[t1],tmp+=g1[t1--];//更新最大值
        f1[++t1]=a[i],g1[t1]=tmp,S+=(LL)a[i]*tmp;
        tmp=1;
        while(t2&&f2[t2]>=a[i])S+=(LL)f2[t2]*g2[t2],tmp+=g2[t2--];//更新最小值
        f2[++t2]=a[i],g2[t2]=tmp,S-=(LL)a[i]*tmp;
        ans+=S;//累加答案
    }
    printf("%lld\n",ans);
    return 0;
}
posted @ 2019-10-06 17:32  辰星凌  阅读(279)  评论(0编辑  收藏  举报