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

14 篇文章 0 订阅
5 篇文章 0 订阅

Description

这里写图片描述

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值