Jzoj5231 序列问题

227 篇文章 3 订阅
153 篇文章 0 订阅
这是一道使用CDQ分治算法解决的序列问题,要求求解序列中连续子序列的最大值与最小值乘积之和。通过预处理前缀最大值和最小值,然后二分找到满足条件的端点,利用指针维护单调性,最终实现O(n log n)的时间复杂度解决方案。
摘要由CSDN通过智能技术生成

给你序列A,定义f(l,r)=max{al,al+1,....,ar} , g(l,r)=min{al,al+1,....,ar}

求ΣΣf(i,j)*g(i,j) { i∈[1,n],j∈[i,n] }

哇第一道cdq分治的题诶,当时好像还不会这个玩意2333

我们cdq分治一下,先求出[1,mid]和[mid+1,r]的答案

让后我们考虑枚举j∈[mid+1,r]

对于每一个j,我们可以通过预处理出前缀min,max再二分出两个端点p1,p2满足

f(p1,j)=g(p1,j)=A[j]

f(p2,p1)=A[j]或者g(p2,p1)=A[j]

最后f(l,p2)和g(l,p2)与A[j]无关

每次统计答案可以分成这三个区间来维护

又发现,由于A[j]的单调,p2和p1肯定是单调递减的,可以用指针来维护,成功将复杂度降为O(n lg n)

#include<stdio.h>
#include<algorithm>
#define M 1000000007
#define L long long
using namespace std;
L mx[500010],mn[500010];
L sx[500010],s[500010];
L sm[500010],sn[500010];
int n;
L cdq(int l,int r){
	if(l==r) return s[l]*s[r]%M;
	int m=l+r>>1,pi=m,pj=m;
	L ans=(cdq(l,m)+cdq(m+1,r))%M;
	mx[m+1]=-M; mn[m+1]=M; 
	sm[m+1]=sn[m+1]=sx[m+1]=0;
	for(int i=m;i>=l;--i){
		mx[i]=max(mx[i+1],s[i]);
		mn[i]=min(mn[i+1],s[i]);
		sm[i]=(sm[i+1]+mx[i])%M;
		sn[i]=(sn[i+1]+mn[i])%M;
		sx[i]=(sx[i+1]+mx[i]*mn[i]%M)%M;
	}
	L rm=-M,rn=M;
	for(int j=m+1;j<=r;++j){
		rm=max(s[j],rm);
		rn=min(s[j],rn);
		while(pi>=l&&mx[pi]<rm) pi--;
		while(pj>=l&&mn[pj]>rn) pj--;
		if(pi>pj){
			ans=(ans+(m-pi)*rm%M*rn%M)%M;
			ans=(ans+rn%M*(sm[pj+1]-sm[pi+1])%M)%M;
			ans=(ans+sx[l]-sx[pj+1]+M)%M;
		} else {
			ans=(ans+(m-pj)*rm%M*rn%M)%M;
			ans=(ans+rm*(sn[pi+1]-sn[pj+1])%M)%M;
			ans=(ans+sx[l]-sx[pi+1]+M)%M;
		}
	}
	return ans;
}
int main(){
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld",s+i);
	printf("%lld\n",(M+cdq(1,n))%M);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值