代码源OJ #846. 第二大数字和;
本文为
O
(
n
)
O(n)
O(n)解法;
思路
我们考虑关于每一个数,以该数为第二大数字和的区间数与在排列中该数左右两侧的第一个和第二个大于该数的数的位置有关;
如果记该数位置为 p ,左侧第一个大于该数的位置为 pl1 ,左侧第二个大于该数的位置为 pl2(右侧同理 pr1,pr2 ),( p r 2 > p r 1 > p > p l 1 > p l 2 pr2>pr1>p>pl1>pl2 pr2>pr1>p>pl1>pl2)则以该点为第二大数字的区间有 ( p l 2 − p l 1 ) ( p r 1 − p ) + ( p r 2 − p r 1 ) ( p − p l 1 ) (pl_2-pl_1)(pr_1-p)+(pr2-pr1)(p-pl1) (pl2−pl1)(pr1−p)+(pr2−pr1)(p−pl1);
在操作过程中,我们选择使用类似链表的数据结构,首先假定所有数的左右两侧数都大于它自身,即 p l 1 = p − 1 , p r 1 = p + 1 pl_1=p-1,pr_1=p+1 pl1=p−1,pr1=p+1 ,从小到大处理每一个数,处理后将其左右的相邻数中的 pl1 和 pr1 更新,并抛弃这个数(类似链表中删除元素)。在这个过程中我们就可以保证每个数的 pl1 与 pr1 在使用时一定是正确的;
在这个过程中,px2 可以通过嵌套表示出, [ p x 1 ] x 1 [px_1]x_1 [px1]x1在被使用时并不是数px1 的x方向第一个大于其的数,而是数p 的x方向第一个大于其的数(因为数据还没有处理到数px1,只处理了小于数p 的部分)。因此我们只需要妥善维护每个点 p 的 pl1 与 pr1 即可;
在下面的代码实现中,存储的并不是 px1,而是 ∣ p − p x 1 ∣ |p-px_1| ∣p−px1∣ ,实际上没有区别;
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct ln{
int l,r;
ll tl,tr;
int num;
}node[100005];
int pos[100005];
int main()
{
int n,tmp;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&tmp);
pos[tmp]=i;
node[i].l=i-1;
node[i].r=i+1;
node[i].tl=node[i].tr=1;
node[i].num=tmp;
}
// node[1].tl=node[n].tr=0;
ll ans=0;
for(ll i=1;i<=n-1;i++)
{
int mk=pos[i];
// printf("*%d %d\n",i,(node[mk].tl*node[node[mk].r].tr+node[mk].tr*node[node[mk].l].tl));
ans+=i*(node[mk].tl*node[node[mk].r].tr+node[mk].tr*node[node[mk].l].tl);
node[node[mk].l].tr+=node[mk].tr;//维护左右邻
node[node[mk].r].tl+=node[mk].tl;
node[node[mk].l].r=node[mk].r;//删除元素
node[node[mk].r].l=node[mk].l;
}
cout<<ans;
return 0;
}