题目链接:点击打开链接
题目大意:
给你n个数,定义一个区间价值为这个区间内的最大值-最小值。计算当前n个数所有区间的价值和。
解题思路:
刚开始写了很久但是没有什么思路,后来还是队友说出了想法,即对每个数计算它作为最大值和最小值的贡献,假设当前区间(l,r),那么如果我们知道当前区间最大值的下标pos,就可以知道它在哪些区间是作为最大值存在的,加上它的贡献,再递归到另外的区间继续同样的操作。这就是比赛当时的思路,其实就是dfs+线段树,但是这题竟然该死的卡内存,要不要这样搞,特别是我写线段树喜欢把l,r什么的全写struct里面,直接空间爆炸,后来去掉之后空间还是炸,遂gg。直到比赛最后也没想出来怎么解决。赛后补题突然想到其实线段树可以只存当前极值和其下标,我们可以build两次分别实现对最大值和最小值的查询,然后拿出比赛代码开始改,就A了,最后也算是卡着内存过去了。
其实还有另外一种写法,思路是一样的,但是不需要线段树,即用4个数组记录当前数作为最大值或最小值能够影响的最左区间和最右区间,其实现在想想当时傻了,没有更新操作的话不应该首选线段树来写的。每个数组可以差不多O(n)的时间维护出来,最后同样分别算每个数的贡献就行了。贴两种做法的代码,
Ac代码:
线段树版:
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int INF=1e9+7;
const int mod=998244353;
int n,a[maxn],pos;
ll res,ans;
int val[maxn<<2],idx[maxn<<2]; //维护极值和其下标
void pushup_1(int rt) //以下都是简单的线段树操作
{
if(val[lson]>val[rson]) val[rt]=val[lson],idx[rt]=idx[lson];
else val[rt]=val[rson],idx[rt]=idx[rson];
}
void pushup_2(int rt)
{
if(val[lson]<val[rson]) val[rt]=val[lson],idx[rt]=idx[lson];
else val[rt]=val[rson],idx[rt]=idx[rson];
}
void build_ma(int l,int r,int rt)
{
int mid=(l+r)>>1;
if(l==r)
{
val[rt]=a[l];
idx[rt]=l;
return ;
}
build_ma(l,mid,lson);
build_ma(mid+1,r,rson);
pushup_1(rt);
}
void build_mi(int l,int r,int rt)
{
int mid=(l+r)>>1;
if(l==r)
{
val[rt]=a[l];
idx[rt]=l;
return ;
}
build_mi(l,mid,lson);
build_mi(mid+1,r,rson);
pushup_2(rt);
}
void query(int pl,int pr,int l,int r,int flag,int rt)
{
int mid=(pl+pr)>>1;
if(l<=pl&&pr<=r)
{
if(flag&&ans<val[rt]) ans=val[rt],pos=idx[rt]; //根据flag决定查询最大还是最小
if(!flag&&ans>val[rt]) ans=val[rt],pos=idx[rt];
return ;
}
if(l<=mid) query(pl,mid,l,r,flag,lson);
if(r>mid) query(mid+1,pr,l,r,flag,rson);
}
void slove(int l,int r,int flag) //递归查询当前的区间的极值和下标
{
if(l>r) return ; //注意递归结束条件
if(l==r)
{
if(flag) res+=a[l];
else res-=a[l];
return ;
}
if(flag)
{
ans=0;query(1,n,l,r,1,1);
res+=((ll)(pos-l+1)*(ll)(r-pos+1)*ans); //把它对答案的贡献更新
int px=pos;
slove(l,px-1,flag); //继续递归左右区间
slove(px+1,r,flag);
}
else
{
ans=INF;query(1,n,l,r,0,1);
res-=((ll)(pos-l+1)*(ll)(r-pos+1)*ans);
int py=pos;
slove(l,py-1,flag);
slove(py+1,r,flag);
}
}
int main()
{
//cout<<sizeof(t)/1024/1024<<endl;
while(scanf("%d",&n)!=EOF)
{
res=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build_ma(1,n,1),slove(1,n,1);
build_mi(1,n,1),slove(1,n,0);
printf("%lld\n",res);
}
//system("pause");
}
正常版本:
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const int INF=1e9+7;
const int mod=998244353;
int n,cnt,a[maxn];
int lmax[maxn],rmax[maxn],lmin[maxn],rmin[maxn];
int main() //四个数组分别记录a[i]作为最大或最小值能够影响的最左和最右
{
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) //初始化为i
lmax[i]=lmin[i]=rmax[i]=rmin[i]=i;
for(int i=2;i<=n;i++) //更新操作
{
cnt=i;
while(cnt>1&&a[i]>=a[cnt-1]) //注意>=
cnt=lmax[cnt-1];
lmax[i]=cnt;
cnt=i;
while(cnt>1&&a[i]<=a[cnt-1])
cnt=lmin[cnt-1];
lmin[i]=cnt;
}
for(int i=n-1;i>=1;i--)
{
cnt=i;
while(cnt<n&&a[i]>a[cnt+1]) //注意>
cnt=rmax[cnt+1];
rmax[i]=cnt;
cnt=i;
while(cnt<n&&a[i]<a[cnt+1])
cnt=rmin[cnt+1];
rmin[i]=cnt;
}
ll res=0;
for(int i=1;i<=n;i++) //计算每个数对答案的贡献即可
{
res+=(ll)(i-lmax[i]+1)*(rmax[i]-i+1)*a[i];
res-=(ll)(i-lmin[i]+1)*(rmin[i]-i+1)*a[i];
}
printf("%lld\n",res);
}
//system("pause");
}