牛客 区区区间间间——单调栈+思维
题目
思路
一看数据范围是1e5就知道这道题大概率是个nlogn的做法,
我们这样考虑:
- 对于任意一个数 a i a_i ai,它作为区间最大值(最小值)出现的次数,最后 a i a_i ai对答案的贡献就是 a i ∗ ( m a x [ i ] − m i n [ i ] ) a_i*(max[i]-min[i]) ai∗(max[i]−min[i]),累加起来即可
- 如何求 a i a_i ai作为最大值出现的次数呢?我们找到左边第一个比它大的数 a x a_x ax,右边第一个比它大的数 a y a_y ay, 那么在区间 [ x + 1 , y − 1 ] [x+1,y-1] [x+1,y−1]范围内, a x a_x ax就是最大的,在这个区间内设左边比它小的数有 x x x个,右边有 y y y个,那么在这个大区间的 y + x ∗ ( y + 1 ) y+x*(y+1) y+x∗(y+1)个子区间内它都是最大值
- 例如数据:1 2 8 4 3, 这个8前面有2个数比它小,右边有2个数比它小,对于前面2个数,比如对于1,[1,2,8]一次,[1,2,8,4]一次,[1,2,8,4,3]一次,共y+1=2+1=3次,共有x=2个数,所以 x ∗ ( y + 1 ) x*(y+1) x∗(y+1), 对于后面2个数[8,4]一次,[8,4,3]一次共 y = 2 y=2 y=2次,所以是y+x*(y+1)次
- 如何求 a i a_i ai左边第一个比它大的数的位置呢,如何求 a i a_i ai右边第一个比它大的数的位置呢——单调栈,没学过单调栈的同学去自学一下
- 既然能够求出 a i a_i ai作为最大值出现的次数,那么 a i a_i ai作为最小值同理即可
- 注意事项:因为这道题数据可能出现多个值相同的元素,那么在写单调栈的时候就得注意,一个是a[stk[tt]]<=a[i],一个是a[stk[tt]]<a[i], 左边严格找到第一个比它小的数,右边找到一个小于等于它的数,否则会出现重复计算
- 例如数据2 1 2, 很明显2只能被加3次,如果都是小于等于,那么对于每个位置的数,可以扩展的为(1,3), (2,2),(1,3),区间(1,3)被重复计算了两次,正确应该是(1,3),(2,2),(2,3)
参考代码
代码写得有点冗长,可以精简一下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
const int N = 1e5+9;
ll mx[N],mi[N];
ll l[N],r[N],a[N];
ll stk[N];
int tt;
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
scanf("%d",&n);
tt=0;
mem(a,0);
mem(stk,0);
for(int i=1; i<=n; i++)
{
scanf("%lld",a+i);
while(tt&&a[stk[tt]]>=a[i]) tt--;
l[i]=stk[tt];
stk[++tt]=i;
}
tt=0;
stk[tt]=n+1;
for(int i=n; i>=1; i--)
{
while(tt&&a[stk[tt]]>a[i]) tt--;
r[i]=stk[tt];
stk[++tt]=i;
ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的
ll y=r[i]-i-1;
mi[i]=y+x*(y+1);
//cout<<mi[i]<<endl;
/// cout<<r[i]<<endl;
}
tt=0;
stk[tt]=0;
for(int i=1; i<=n; i++)
{
while(tt&&a[stk[tt]]<=a[i]) tt--;
l[i]=stk[tt];
// cout<<a[l[i]]<<endl;
stk[++tt]=i;
}
tt=0;
stk[tt]=n+1;
for(int i=n; i>=1; i--)
{
while(tt&&a[stk[tt]]<a[i]) tt--;
r[i]=stk[tt];
stk[++tt]=i;
ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的
ll y=r[i]-i-1;
mx[i]=y+x*(y+1);
// cout<<r[i]<<endl;
// cout<<x<<" "<<y<<endl;
// cout<<mx[i]<<endl;
}
ll ans=0;
for(int i=1; i<=n; i++)
{
//cout<<mi[i]<<" "<<mx[i]<<endl;
ans+=a[i]*(mx[i]-mi[i]);
}
printf("%lld\n",ans);
}
return 0;
}
优化版代码
我们可以发现,当 a i a_i ai作为最小值在答案中出现的次数等价于 − a i -a_i −ai最为最大值出现的次数,所以我们求 a i a_i ai作为最小值对答案的贡献时,令a[i]=-a[i],跑一遍作为最大值的函数即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
const int N = 1e5+9;
ll mx[N];
ll l[N],r[N],a[N];
ll stk[N];
int tt,n;
ll solve()
{
mem(stk,0);
tt=0;
for(int i=1; i<=n; i++)
{
while(tt&&a[stk[tt]]<=a[i]) tt--;
l[i]=stk[tt];
stk[++tt]=i;
}
tt=0;
stk[tt]=n+1;
for(int i=n; i>=1; i--)
{
while(tt&&a[stk[tt]]<a[i]) tt--;
r[i]=stk[tt];
stk[++tt]=i;
ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的
ll y=r[i]-i-1;
mx[i]=y+x*(y+1);
}
ll res=0;
for(int i=1; i<=n; i++)
{
//cout<<a[i]<<" "<<mx[i]<<endl;
res+=a[i]*mx[i];
}
// cout<<res<<endl;
return res;
}
int main()
{
int t;
cin>>t;
while(t--)
{
scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%lld",a+i);
ll ans=solve();
//当a[i]最为最小值的时候出线的次数,等于-a[i]作为最大值出现的次数
for(int i=1; i<=n; i++) a[i]=a[i]*(-1);
printf("%lld\n",ans+solve());
}
return 0;
}
总结
本文写得有点啰嗦,如果你有什么批评或者好的建议或者疑问,欢迎留言!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通