题解:[ABC140E] Second Sum

题目传送门

洛谷同步链接

题意分析

\(a_i\) 对答案产生贡献时,当且仅当存在区间 \([l,r]\) 满足 \(i\in[l,r]\)\(a_i\)\([l,r]\) 的次大值。

由此我们开始做题。

计算合法区间数

显然,当且仅当区间中有且仅有一个数大于 \(a_i\) 时,答案会增加 \(a_i\)

那么我们考虑算出,对于每一个 \(a_i\),会造成多少个 \(a_i\) 的贡献。

如果我们找出了 \(a_i\) 左右两边第一个、第二个大于 \(a_i\) 的数,从左至右分别记作 \(a_{dl_i},a_{l_i},a_{r_i},a_{dr_i}\)(满足 \(dl_i<l_i<i<r_i<dr_i\) 且 $ a_{dl_i},a_{l_i},a_{r_i},a_{dr_i}>a_i$),那么这四个数中肯定只能有一个\(a_i\) 处于同一个区间(否则 \(a_i\) 不为次大值)。

那么我们所需要的会对答案造成贡献的区间就不会包含 \(a_{dl_i}\)\(a_{dr_i}\)(因为一旦包含 \(a_{dl_i}\),就一定会包含 \(d_{l_i}\)\(r_i,dr_i\) 同理)。

既然如此,令合法区间为 \([L,R]\),合法情况就只有两种:

  • \(L\in[dl_i+1,l_i],R\in[i,r_i-1]\),区间包含了 \(l_i\),记作 \([L_1,R_1]\)
  • \(L\in[l_i+1,i],R\in[r_i,dr_i-1]\),区间包含了 \(r_i\),记作 \([L_2,R_2]\)

图示:

\(L\in[dl_i+1,l_i],R\in[i,r_i-1]\) 时,则合法区间数为 \(\left(l_i-\left(dl_i+1\right)+1\right)\left(\left(r_i-1\right)-i+1\right)=(l_i-dl_i)(r_i-i)\)

同样地,当 \(L\in[l_i+1,i],R\in[r_i,dr_i-1]\) 时,合法区间数为 \(\left(i-l_i\right)\left(dr_i-r_i\right)\)

那么 \(a_i\) 对答案的总贡献就是 \(a_i\times\left[\left(l_i-dl_i\right)\left(r_i-i\right)+\left(i-l_i\right)\left(dr_i-r_i\right)\right]\),可以 \(\mathcal O\left(1\right)\) 求解。

则最终答案为 \(\large \sum\limits_{i=1}^n{a_i\times\left[\left(l_i-dl_i\right)\left(r_i-i\right)+\left(i-l_i\right)\left(dr_i-r_i\right)\right]}\)(下标从 \(1\) 开始)。

但这时我们需要注意的是,这四个数都有可能不会存在,那么初值就变得十分重要,甚至会影响最终答案的正确性当然,你不嫌麻烦你特判也行。

  • 如果你的下标从 \(1\) 开始:

    for(int i=1;i<=n;i++){
        l[i]=dl[i]=0;
        r[i]=dr[i]=n+1;
    }
    
  • 如果你的下标从 \(0\) 开始:

    for(int i=0;i<n;i++){
        l[i]=dl[i]=-1;
        r[i]=dr[i]=n;
    }
    

(其实就是类似于“值域两端”)

解释一下为什么:(下标从 \(1\) 开始,从 \(0\) 开始和从 \(1\) 开始差不多,不能理解可以手推或参见上图)

\(l_i,dl_i\) 为例,\(r_i,dr_i\) 同理可得。

首先,\(l_i=dl_i=0\) 的好处就是选取区间时 \(l_i+1=dl_i+1=1\)(不存在的话)。

如果 \(l_i\) 不存在,那么 \(dl_i\) 绝对不存在:\(l_i=dl_i=1,l_i-dl_i=0\),上文中 \([L_1,R_1]\) 的区间因为要选取 \(l_i\) 自然不存在,正确;对于 \([L_2,R_2]\),已经在右边选取了 \(r_i\)\(l_i\) 自然不会选取到,不影响答案。

如果 \(l_i\) 存在,\(dl_i\) 不存在:\([L_1,R_1]\) 选取了 \(l_i\),则 \(1\sim l_i\) 都可以选取,此时 \(l_i-dl_i=l_i-0=l_i\),正确;对于 \([L_2,R_2]\),同样与 \(l_i,dl_i\) 无关,不影响答案。

这样,我们便可以 \(\mathcal O\left(n\right)\) 求出最终答案。

如何计算四个数的位置

如果你不知道 set

说了这么多,我们还是不知道如何求出 \(dl_i,l_i,r_i,dr_i\)

事实上,我们仅仅需要从大至小set 中加入 \(a_i\),然后利用前驱、后继节点来判断大小关系即可。

\(p_i\) 表示 \(a_i\) 的下标,那么按照 \(a_i\) 值排序,使得 \(\large a_{p_1}>a_{p_2}>a_{p_3}>\cdots>a_{p_n}\)

这时我们用 \(i\) 遍历 \([1,n]\),每次在 set 中执行 lower_bound(p[i])(或者 upper_bound(p[i]) 也行,因为 \(p_i\) 不重复),找到第一个大于 \(p_i\) 的下标 \(p_x\)

那么 \(p_x\)\(p_i\) 之前插入 set,代表 \(a_{p_x}>a_{p_i}\),又有 \(p_x>p_i\)\(p_x\) 最小,则 \(p_x=r_{p_i}\)

\(dr_{p_i}\) 也很简单,当 \(p_x\) 不为 set 的最后一个元素(不然越界)时,找到 \(p_x\)后继节点即可。由 set 的有序特性且 \(p_i\) 不重复,\(p_x\) 的后继节点就是最小的大于 \(p_x\) 的元素 \(p_{x'}\)\(dr_{p_i}=p_{x'}\)

对于 \(l_{p_i}\),其实就是 \(p_{x}\)前驱节点 \(p_y\)因为 set\(p_i\) 不重复、\(p_x\) 最小且 \(p_y<p_x,p_x>p_i\),有 \(p_y<p_i\)\(p_y\) 最大。

那么就有 \(l_{p_i}=p_y\)。和 \(dr_{p_i}\) 同理,\(dl_{p_i}\)\(p_y\) 的前驱节点 \(p_{y'}\)

注意判定越界

最后记得将 \(p_i\) 插入 set 即可。

不会有人不会求后继节点、前驱节点吧?

使用 next()prev() 即可。

虽然说对迭代器进行自减运算、自加运算,或者进行数学运算后强制类型转换也行,但是确实没有这两个简单。

注意事项

  • \(dl_i,l_i,r_i,dr_i\) 的初始值。
  • long long

AC代码

//#include<bits/stdc++.h>
#include<algorithm> 
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
#include<set> 
using namespace std;
typedef long long ll;
const int N=2e5;
ll n,a[N+1],p[N+1],l[N+1],dl[N+1],r[N+1],dr[N+1];
bool cmp(ll x,ll y){
	if(a[x]!=a[y])return a[x]>a[y];
	return x<y;
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)scanf("%lld",a+i);
	for(ll i=1;i<=n;i++){
		p[i]=i;
		l[i]=dl[i]=0;
		r[i]=dr[i]=n+1;
	}sort(p+1,p+n+1,cmp);
	set<ll>s;
	for(ll i=1;i<=n;i++){
		auto pl=s.lower_bound(p[i]);
		if(pl!=s.end()){
			r[p[i]]=*pl;
			if(next(pl)!=s.end())dr[p[i]]=*next(pl);
		}if(pl!=s.begin()){
			pl--;
			l[p[i]]=*pl;
			if(pl!=s.begin())dl[p[i]]=*prev(pl);
		}s.insert(p[i]);
	}ll ans=0;
	for(ll i=1;i<=n;i++)ans+=a[i]*((l[i]-dl[i])*(r[i]-i)+(i-l[i])*(dr[i]-r[i]));
	printf("%lld\n",ans);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2025-07-20 12:57  TH911  阅读(9)  评论(0)    收藏  举报