输入描述:
第一行输入数据组数T
对于每组数据,第一行为一个整数n,表示序列长度
接下来一行有n个数,表示序列内的元素
输出描述:
对于每组数据,输出一个整数表示答案
示例1
输入
3
3
4 2 3
5
1 8 4 3 9
20
2 8 15 1 10 5 19 19 3 5 6 6 2 8 2 12 16 3 8 17
输出
5
57
2712
说明
对于一组测试数据的解释:
区间[1, 2]的贡献为:4 - 2 = 2
区间[1, 3]的贡献为:4 - 2 = 2
区间[2, 3]的贡献为:3 - 2 = 1
2 + 1 + 2 = 5.
备注:
不保证数据随机生成!
单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小
单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大
单调递增栈可以保证,对于当前元素来说,栈顶元素是第一个大于当前元素的数
单调递增栈伪代码:
insert x
while(!sta.empty() && sta.top()<x)
sta.pop()
sta.push(x)
分析:
首先,问题可以转化为求
其中mx_ij是从i-j区间内的最大值,mn_ij是从i-j区间内的最小值
可以通过单调栈维护每个点作为最大值的区间,统计以该点为最大值的区间数量
以a[i]为区间最大值,向左维护到l[i],向右维护到r[i],即从l[i]-r[i]区间内a[i]都是最大值
数量的求法:
分两种情况:
①:a[i]作为端点,即从l[i]到r[i]内再选一点作为另一个端点,共有r[i]-l[i]种情况
②:a[i]作为区间中的一个点,即从l[i]到i内选一个左端点,i到r[i]内选一个右端点,共有(i-l[i])*(r[i]-i)种情况
两种情况求和乘上a[i]即可求出以a[i]为最大值的区间的和
最小值的和可以把a[i]=-a[i]即可求出最小值的和,与前面的负号正好抵消
细节部分
左端点:单调递增栈(找第一个大于ai的数,下标+1)
右端点:单调不减栈(找第一个大于等于ai的数,下标-1)
只要两个中有一个是不减的就可以,这么设计是为了处理连续出现相同数的情况如:
8 6 6 2 7
1 2 3 4 5
如果两个栈都单调递增(找第一个大于ai的数)
那么对于第一个6,最大区间为[2,4],当前下标为2,所有区间为[2,3],[2,4]
对于第二个6,最大区间为[2,4],当前下标为3,所有区间为[2,3],[3,4],[2,4]
可以发现[2,3],[2,4]这两个区间算了两遍,答案错误
如果左端点向左找第一个大于ai的数(单调递增栈),右端点找第一个大于等于ai的数(单调不减栈)
那么对于第一个6,最大区间为[2,2],当前下标为2,所有区间为null
对于第二个6,最大区间为[2,4],当前下标为3,所有区间为[2,3],[3,4],[2,4]细节部分
左端点:单调递增栈(找第一个大于ai的数,下标+1)
右端点:单调不减栈(找第一个大于等于ai的数,下标-1)
只要两个中有一个是不减的就可以,这么设计是为了处理连续出现相同数的情况如:
8 6 6 2 7
1 2 3 4 5
如果两个栈都单调递增(找第一个大于ai的数)
那么对于第一个6,最大区间为[2,4],当前下标为2,所有区间为[2,3],[2,4]
对于第二个6,最大区间为[2,4],当前下标为3,所有区间为[2,3],[3,4],[2,4]
可以发现[2,3],[2,4]这两个区间算了两遍,答案错误
如果左端点向左找第一个大于ai的数(单调递增栈),右端点找第一个大于等于ai的数(单调不减栈)
那么对于第一个6,最大区间为[2,2],当前下标为2,所有区间为null
对于第二个6,最大区间为[2,4],当前下标为3,所有区间为[2,3],[3,4],[2,4]
这样就避免了重复计算区间的情况。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100005],l[100005],r[100005];
ll solve(ll n)
{
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
stack<ll> s,t;
//单调递增栈,站内元素从栈底到栈顶递减,也就是只有比栈顶元素小,才能进栈,当比栈顶元素大的时侯,要把大的元素弹栈,再让当前元素进栈,由此来维护一个单调栈
//找a[i]元素对应的左区间
//这里是维护了一个单调栈,因为进栈的元素是下标i,栈内的有序的其实是进栈元素i对应的a[i]的值,
for(int i=1;i<=n;i++)
{
while(s.size()&&a[i]>=a[s.top()])//(1)
{
s.pop();
}
if(!s.size()) l[i]=1;//左边元素都比a[i]小
else l[i]=s.top()+1;//否则左区间为栈顶值+1
s.push(i);//更新单调递增栈
}
//单调递增栈,从a[n]读到a[1];
//找a[i]元素对应的右区间
for(int i=n;i>=1;i--)
{
while(t.size()&&a[i]>a[t.top()])//(2)等于时不pop
//(1),(2)只能有一处等于
{
t.pop();
}
if(!t.size()) r[i]=n;
else r[i]=t.top()-1;
t.push(i);
}
ll ans=0;
for(int i=1;i<=n;i++)
{
ans+=a[i]*(r[i]-l[i]+(i-l[i])*(r[i]-i));
}
return ans;
}
int main()
{
ll t;
cin>>t;
while(t--)
{
ll n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
ll ans=solve(n);
for(int i=1;i<=n;i++) a[i]=-a[i];
ans+=solve(n);
cout<<ans<<endl;
}
return 0;
}