【JZOJ5231】【NOIP2017模拟A组模拟8.5】序列问题

时间:2021-11-25 20:32:12

Description

【JZOJ5231】【NOIP2017模拟A组模拟8.5】序列问题

Data Constraint

对于30%的数据,n<=5000
对于60%的数据,n<=50000
对于100%的数据,n<=500000,0<=A[i]<=10^9

Solution

这道题有很多种解法,我这里主要讲讲分治和线段树两种解法。
分治:我们对于每一次二分出来的mid,我们考虑4种情况:最大值最小值在同一边且跨过mid(左右),最大值最小值不在同一边且跨过mid(左右)。
我们设mx[i]表示mid到i的最大值。mi[i]同理。
对于最大值最小值在同一边且跨过mid的情况:我们可以枚举一边,另一边用一个指针单调移动,强制要求maxi>maxj&&mini

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=5e5+5,mo=1e9+7;
ll a[maxn],d[maxn],mx[maxn],mi[maxn],sum[maxn];
ll n,i,t,j,k,x,y,z,num,ans;
void dg(int l,int r){
if (l==r){
ans=(ans+a[l]*a[l]%mo)%mo;return;
}
if (l>r) return;int mid=(l+r)/2;
mi[mid+1]=mx[mid+1]=a[mid+1];mi[mid]=mx[mid]=a[mid];
for (i=mid+2;i<=r;i++)
mi[i]=min(mi[i-1],a[i]),mx[i]=max(mx[i-1],a[i]);
for (i=mid-1;i>=l;i--)
mi[i]=min(mi[i+1],a[i]),mx[i]=max(mx[i+1],a[i]);
sum[l-1]=0;
for (i=l;i<=mid;i++)
sum[i]=sum[i-1]+mi[i];
j=mid;
for (i=mid+1;i<=r;i++){
while (mi[j]>=mi[i] && mx[j]<=mx[i] && j>=l) j--;
ans=(ans+mx[i]*mi[i]%mo*(mid-j)%mo)%mo;
}
j=mid+1;
for (i=mid;i>=l;i--){
while (mi[j]>=mi[i] && mx[j]<=mx[i] && !(mi[j]==mi[i] && mx[j]==mx[i])&& j<=r) j++;
ans=(ans+mx[i]*mi[i]%mo*(j-mid-1)%mo)%mo;
}
j=mid;k=mid;
for (i=mid+1;i<=r;i++){
while (mx[j]<mx[i] && j>=l)j--;
while (mi[k]>=mi[i] && k>=l) k--;
if (j+1<=k)ans=(ans+(sum[k]-sum[j])%mo*mx[i]%mo)%mo;
}
j=mid;k=mid;
for (i=l;i<=mid;i++)
sum[i]=sum[i-1]+mx[i];
for (i=mid+1;i<=r;i++){
while (mx[j]<=mx[i] && j>=l)j--;
while (mi[k]>mi[i] && k>=l) k--;
if (k+1<=j)ans=(ans+(sum[j]-sum[k])%mo*mi[i]%mo)%mo;
}
dg(l,mid);dg(mid+1,r);
}
int main(){
freopen("seq.in","r",stdin);freopen("seq.out","w",stdout);
scanf("%lld",&n);
for (i=1;i<=n;i++)
scanf("%lld",&a[i]);
dg(1,n);
printf("%lld\n",ans);
}