如果序列中数据都不重复,那么这个题目难度会小很多。本文介绍三种解法。
(1)暴力枚举法
暴力枚举子序列左右端点i和j,检查a[i+1]到a[j-1]间是否有比端点更小的值,复杂度O(N^3)。可得20分。
(2)RMQ优化
RMQ(Range Minimum/Maximum Query)可以看成是一种查找结构,能快速找到区间内极值,每次查找的复杂度是O(1)级别。这样可以把上述算法“检查a[i+1]到a[j-1]间是否有比端点更小的值”优化,整个算法复杂度O(N^2),可得70分。
介绍一种骗分技巧,出题人在出大数据的时候,例如上面100000级别数据,经常会出所有数据全部相同的数据点,或者是1,2,3,4.....100000这样的数据点。如果最后实在没有好方法,且时间充足,可编写代码针对这些特殊情况做处理。此方法不保证有效性!!!
(3)单调栈
经验之谈:如果枚举a[i]为序列次小值,找到数据a[i]左右两端第一个比他小得的问题,这是典型的单调栈问题。读者必须掌握单调栈基础用法才可以阅读下面解题思路。
此题目可以考虑枚举第i个元素,a[i]向左找到第一个比a[i]小的值a[k],此时[k,i]区间一定满足条件,
那么是否能继续向左找更小呢?模拟后发现不可以,例如序列1 3 6 5 ,5向左找到3就可以停下来了,以5为次小的序列是(3,6, 5),(1,3,6,5)是不行的。当然也有可能i元素左侧没有比它小的,这样就找不到满足条件子序列。
问题转换为对于a[i],左右两侧是否存在比它小的值。
如果序列中数据都不重复,按上述分析用单调栈双向各做一次,枚举i元素为次小时所有情况即可得到结果。如果数据有重复呢?
对于重复数据,比如1,3,1,4,1,第5个1为次小元素,左边能和它搭配有两个1,而单调栈处理时碰见满足条件的就会停止出栈操作。这样的话需要调整下栈结构,使其不要存储重复元素,但又能知道重复元素有多少个。方法很多,例如入栈元素用结构体,或者用计数器(map)记录栈中相同值元素个数(代码用map)。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int n,a[100005];
ll ans=0;
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int i,j;
cin>>n;
for(i=1; i<=n; i++)
cin>>a[i];
stack<int>st;
map<int,int>mp;/**< mp计数器,用于记录栈中相同元素的个数,确保栈中元素一定递增,不会有相同值 */
for(i=1; i<=n; i++)
{
while(st.size()&&a[st.top()]>a[i])
{
mp[a[st.top()]]=0;/**< 出栈计数器清零 */
st.pop();
}
if(st.empty())
st.push(i);
else
{
if(a[i]>a[st.top()]) /**< 这个是计算a[i]为次小值,左边比a[i]大的都已经出栈了,栈顶比a[i]小 */
ans++,st.push(i);
else if(a[i]==a[st.top()])
{/**< 处理栈顶和a[i]相同的情况,比如例如这种情况3 3 3 3,第4个3和左边的几个3都能搭配 */
ans+=mp[a[st.top()]];/**< 注意这个时候不要进栈,后面会把栈顶值mp[a[i]]++ */
if(st.size()>1)/**< 1 3 3 3 3,这时第4个3不但和左边3能搭配,还能和1搭配,判断下栈里还有没有比a[i]小的 */
ans++;
}
}
mp[a[i]]++;
}
while(st.size())
st.pop();
for(i=n; i>=1; i--)/**< 反向就无需处理重复元素了 */
{
while(st.size()&&a[st.top()]>=a[i])
st.pop();
if(!st.empty())
ans++;
st.push(i);
}
cout<<ans;
return 0;
}
作者在现场写码时只正向一次单调栈就解决了。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int n,a[100005];
ll ans=0;
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int i,j;
cin>>n;
for(i=1; i<=n; i++)
cin>>a[i];
stack<int>st;
map<int,ll>mp;/**< mp计数器,用于记录栈中相同元素的个数,确保栈中元素一定递增,不会有相同值 */
for(i=1; i<=n; i++)
{
while(st.size()&&a[st.top()]>a[i])
{/**< 下面这句话实际上是计算a[i]为最小值情况,因为凡是出栈元素,其值均大于a[i]
为什么用mp,比如序列 3 3 3 3 1,此时栈里面只有一个3,但是mp[3]=4,而1和左边的4个3都能构成序列 */
ans+=mp[a[st.top()]];
mp[a[st.top()]]=0;/**< 出栈计数器清零 */
st.pop();
}
if(st.empty())
st.push(i);
else
{
if(a[i]>a[st.top()]) /**< 这个是计算a[i]为次小值,左边比a[i]大的都已经出栈了,栈顶比a[i]小 */
ans++,st.push(i);
else if(a[i]==a[st.top()])
{ /**< 处理栈顶和a[i]相同的情况,比如例如这种情况3 3 3 3,第4个3和左边的几个3都能搭配 */
ans+=mp[a[st.top()]];/**< 注意这个时候不要进栈,后面会把栈顶值mp[a[i]]++ */
if(st.size()>1)/**< 1 3 3 3 3,这时第4个3不但和左边3能搭配,还能和1搭配,判断下栈里还有没有比a[i]小的 */
ans++;
}
}
mp[a[i]]++;
}
cout<<ans;
return 0;
}