6637. 【GDOI2020.5.16模拟】Three

题目

给你一个数列(保证两两数字不相同)。
对于一个区间,它的“价值”定义为前三大数的乘积。
求所有的长度大于等于 3 3 3的区间的价值之和。


思考历程

论自闭是什么感受……
当旁边人都切了,都在说这题是水题,但却想不出来的时候……

在最后不到一个钟的时间里用我那愚钝至极的大脑想出了个常数极大的做法:
按照套路,右端点扫过去,维护每个左端点到这个右端点的信息(最大值、次大值、第三大值)。
想象暴力修改是什么样子。将最大值、次大值、第三大值的序列从上到下并列地写在一起。
当对一个位置进行修改的时候,找到这个值插入的位置(最大值上面、最大值和次大值之间、次大值和第三大值之间、第三大值之后),将它插进去,然后它下面的都往下压一位。
而且,很显然随着左端点递增,最大值(或次大值、第三大值)不上升。
于是可以通过二分求出,新加入的这个值在后面的某个区间中,插在最大值上面;然后在这个区间左端点前再二分出某个区间,插在最大值和次大值之间……

然后我就有了个很暴力的做法。首先是要用个线段树来维护一下每个位置的价值,支持区间乘。
分别对于最大值、次大值、第三大值,用平衡树来维护若干个区间。
插入某个地方的时候,将一段区间往下压(最大值变成次大值、次大值变成第三大值那样),用平衡树可以维护。至于那个被压没的第三大值,将和这个区间相交的若干个区间找出来暴力处理。
其实就是珂朵莉树的升级版啦。

时间复杂度 O ( n lg ⁡ n ∗ 巨 大 常 数 ) O(n \lg n*巨大常数) O(nlgn)


正解

这题的正解有很多种,这里讲一个最简单的做法。

对于某个位置 x x x,求当 a x a_x ax作为第三大值的时候的贡献。
它能做出的贡献只有这些情况:

  1. a x a_x ax和上个比 a x a_x ax大的值和上上个比 a x a_x ax大的值。
  2. a x a_x ax和上个比 a x a_x ax大的值和下个比 a x a_x ax大的值。
  3. a x a_x ax和下个比 a x a_x ax大的值和下下个比 a x a_x ax大的值。

所以,可以找到前面和后面三个比 a x a_x ax大的值(第三个要用来计算区间范围)。

直观的思路是,将 a x a_x ax从大到小插入,维护一个链表。
将最终得到的链表建出来,反过来断边到初始状态,这样就可以求出每个 a x a_x ax在插入的时候的前驱后继。
不算排序 O ( n ) O(n) O(n)排序可以基数排序搞到线性复杂度


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000010
#define ll long long
#define mo 1000000007
int n;
ll a[N],p[N];
bool cmpp(int x,int y){return a[x]<a[y];}
int pre[N],suc[N];
int main(){
	freopen("three.in","r",stdin);
	freopen("three.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%lld",&a[i]),p[i]=i;
	sort(p+1,p+n+1,cmpp);
	for (int i=1;i<=n;++i)
		pre[i]=i-1,suc[i]=i+1;
	pre[0]=0,suc[0]=1,pre[n+1]=n,suc[n+1]=n+1;
	for (int i=1;i<=n;++i){
		int x=p[i];
		suc[pre[x]]=suc[x];
		pre[suc[x]]=pre[x];
	}
	ll ans=0;
	for (int i=n;i>=1;--i){
		int x=p[i];
		suc[pre[x]]=x;
		pre[suc[x]]=x;
		ll l1=pre[x],l2=pre[l1],l3=pre[l2];
		ll r1=suc[x],r2=suc[r1],r3=suc[r2];
		ans+=((l2-l3)*(r1-x)%mo*(a[l2]*a[l1]%mo)+(l1-l2)*(r2-r1)%mo*(a[l1]*a[r1]%mo)+(x-l1)*(r3-r2)%mo*(a[r1]*a[r2]%mo))%mo*a[x]%mo;
	}
	printf("%lld\n",ans%mo);
	return 0;
}

总结

感觉自己的脑子太废了。
以后见到序列上的问题时,不要总是思想僵化,只是想到枚举右端点维护左端点的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值