A. 数列求和(1s,256MiB)
【题目描述】
定义一个数列的价值为:数列中最大的一个数减去最小的一个数(即该数列的极差)。
例如:数列 3 , 1 , 7 , 2 3,1,7,2 3,1,7,2 的价值为 6 = 7 − 1 6=7-1 6=7−1;数列 42 , 42 42,42 42,42 价值为 0 = 42 − 42 0=42-42 0=42−42。
现在给你一个数列,要你求出所有连续子数列的价值总和。
【输入格式】
第一行为 n n n,表示数列的长度。
接下来 n n n 行,每行为一个不大于 1 0 8 10^8 108 的正整数,表示数列中的每个元素。
【输出格式】
输出所有连续子序列的总和。
【样例】
【输入样例】
3
1
2
3
【输出样例】
4
想法1
每一个数都可能成为一个区间的最大值和最小值对答案产生贡献,用两个单调栈维护一下每个数成为区间最大值(最小值)的左右范围,最后统一计算答案即可。
时间复杂度
O
(
n
)
O(n)
O(n)。(考试的时候一直在想st表,丝毫没有想到可以用单调栈…)
【代码1】(单调栈写法)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int a[300001],n,l[300001],r[300001],ll[300001],rr[300001];
int st[300001],top;
long long ans;
int main(){
//freopen("sequence.in","r",stdin);
//freopen("sequence.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
r[i]=rr[i]=n+1;
}
for(int i=1;i<=n;++i){
while(top&&a[st[top]]>a[i])
r[st[top--]]=i;
l[i]=st[top];
st[++top]=i;
}
memset(st,0,sizeof(st));
top=0;
for(int i=1;i<=n;++i){
while(top&&a[st[top]]<a[i])
rr[st[top--]]=i;
ll[i]=st[top];
st[++top]=i;
}
for(int i=1;i<=n;++i){
ans-=1ll*(i-l[i])*(r[i]-i)*a[i];
ans+=1ll*(rr[i]-i)*(i-ll[i])*a[i];
}
printf("%lld\n",ans);
return 0;
}
想法2
使用st表维护区间最大值和最小值.
记函数 s o l v e ( l , r ) solve(l,r) solve(l,r) 表示计算区间 [ l , r ] [l,r] [l,r] 的所有子区间的最大值贡献。记 p p p 为区间 [ l , r ] [l,r] [l,r] 的最大值所在位置,则所有跨过 p p p 的区间的最大值均为 a p a_{p} ap,故最大值 a p a_{p} ap 的贡献为 ( p − l + 1 ) ( r − p + 1 ) a p (p−l+1)(r−p+1)a_p (p−l+1)(r−p+1)ap。接着递归调用 s o l v e ( l , p − 1 ) solve(l,p−1) solve(l,p−1), s o l v e ( p + 1 , r ) solve(p+1,r) solve(p+1,r),计算其余区间的贡献即可。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
【代码2】(st表写法)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m;
int f_max[300011][32];
int f_min[300011][32];
int i_max[300011][32];
int i_min[300011][32];
int lg[1000001];
int a[300011];
long long ans;
int rm_max(int x,int y){
int k=lg[y-x+1];
if(f_max[x][k]>f_max[y-(1<<k)+1][k])return i_max[x][k];
else return i_max[y-(1<<k)+1][k];
}
int rm_min(int x,int y){
int k=lg[y-x+1];
if(f_min[x][k]<f_min[y-(1<<k)+1][k])return i_min[x][k];
else return i_min[y-(1<<k)+1][k];
}
void solve_max(int l,int r){
if(l>r)return;
int p=rm_max(l,r);
ans+=1ll*a[p]*(p-l+1)*(r-p+1);
solve_max(l,p-1);
solve_max(p+1,r);
}
void solve_min(int l,int r){
if(l>r)return;
int p=rm_min(l,r);
ans-=1ll*a[p]*(p-l+1)*(r-p+1);
solve_min(l,p-1);
solve_min(p+1,r);
}
int main(){
//freopen("sequence.in","r",stdin);
//freopen("sequence.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
a[i]=f_max[i][0]=f_min[i][0]=read();
for(int i=1;i<=n;i++)
i_min[i][0]=i_max[i][0]=i;
for(int i=2;i<=n;i++)
lg[i]=lg[i>>1]+1;
for(int l=1;l<=lg[n];l++){
for(int i=1;i+(l<<1)-1<=n;i++){
if(f_max[i][l-1]>f_max[i+(1<<(l-1))][l-1]){
f_max[i][l]=f_max[i][l-1];
i_max[i][l]=i_max[i][l-1];
}
else{
f_max[i][l]=f_max[i+(1<<(l-1))][l-1];
i_max[i][l]=i_max[i+(1<<(l-1))][l-1];
}
if(f_min[i][l-1]<f_min[i+(1<<(l-1))][l-1]){//转移最大(小)值的下标
f_min[i][l]=f_min[i][l-1];
i_min[i][l]=i_min[i][l-1];
}
else{
f_min[i][l]=f_min[i+(1<<(l-1))][l-1];
i_min[i][l]=i_min[i+(1<<(l-1))][l-1];
}
}
}
solve_max(1,n);
solve_min(1,n);
cout<<ans<<endl;
}
【想法3】(不怎么会写,学习中)
想法2中分治过程的分治树即为笛卡尔树,将原序列的笛卡尔树建出后计算,时间复杂度 O ( n ) O(n) O(n)