COCI. DIFERENCIJA(序列处理中的小技巧)


DIFERENCIJA

题目类型:虚拟题目 时间限制:1.0s 空间限制:64.0MB 提交文件大小限制:100.0KB 提示:%I64d & %I64u
抓题完成
序列的值被定义成其中最大的元素和最小的元素的中间的数的个数。如序列(3, 1, 7, 2) 的值为6, 序列(42, 42)的值为0.现给定一个序列,要求出所有连续子序列的值的和。
输入:
第一行一个整数 N (2 ≤ N ≤ 300 000), 表示序列的元素个数。
接下来N行,每行一个正整数值不超过 100000 000.
输出:
一个数,表示所有子序列的值的和
EXAMPLE TEST DATA
input
3
1
2
3
output
4
 

题目大意:

给定一段序列,一段序列的权值是这段权值的最大值减去最小值,求每段序列的权值之和


做法:

Σimax-min 单调栈维护


分析:
可以知道最后答案是Σimax-min
那么可以分开求得Σimax和Σmin

如果我们有一个函数能求的序列的imax总和
我们可以把序列全部取反,最小值边最大值
可以同一个函数求得min总和

我们可以考虑一个值是多少区间的最大值
维护一个从大到小的单调栈
前面的可以直接求得 第一个比他大的值的位置last

last到i之间的序列就是a[i]为imax
我们再考虑他右边第一个比他大的位置怎么求
求得右边的长度,a[i]的个数就是左边*右边

因为是左边个数*右边个数
所以右边每加入一个 就会多一个左边个数

这里有个技巧就是我们记一个累加和now

表示我们记得是还没确定右边序列的点的左边序列长度之和

这样我们就不用一次次地加,我们加在一起累加,复杂度就为n

如果被弹出栈就减去对应的个数

这样每次加now 就把左边所以应该加的一次次的加上了


附上代码:
 
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3e5+12;
int n;
int a[N];
long long ans;
int stack[N],temp;
long long calc()
{
    temp=1;
    long long now=0;
    long long sum=0;
    for(int i=1;i<=n;i++) 
    {
        while(temp>1&&a[stack[temp]]<a[i]) //维护一个从大到小的栈 
        {
            now-=1LL*(stack[temp]-stack[temp-1])*a[stack[temp]];//他不能再最为最大值值,右边不能再扩展了 
            temp--;
        }
        now+=1LL*(i-stack[temp])*a[i];//如果前面一个点没有被影响说明它比这个大 他会一直累加 
        sum+=now;//相当于加了左边的所有可以为当前最大值的个数 
        stack[++temp]=i;
    }
    return sum;
}

int main()
{
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    ans+=calc();
    for(int i=1;i<=n;i++) a[i]=-a[i];
    ans+=calc();
    printf("%lld
",ans);
    return 0;
}
View Code

原文地址:https://www.cnblogs.com/Heey/p/9028881.html