一道分治题

一道分治题

题目大意:

有n个数分别为a1,a2,……,an,
for(i=1;i<=n;i++)
for(j=i;j<=n;j++)
ans+=(j-i+1)*min(i~j)*max(i~j)
求ans,n<=500000

solution:

T2考虑分治,首先想到的做法是每次找最大值,以最大值为中心分成两段,时间复杂度Ω(n lgn)。可是一个有序的数列会把它卡成n²,所以一定要找中间值把区间断开。令mid=(l+r)/2,对于i∈[l,mid],求出i~mid的后缀Min[i]、Max[i],对于i∈[mid+1,r],求出mid+1~i的前缀Min[i],Max[i]。由于使用分治,所以只要考虑左端点i∈[I,mid],右端点j∈[mid+1,r]的区间对答案的贡献。要考虑4种情况,就是最大值在左边或右边,最小值在左边还是右边。为了解决数字大小相同的问题,在大小相同时,默认靠前的值比较小。
1、 Min[i]<=Min[j]且Max[i]>=Max[j]从大到小枚举i,随着i的减小,j的合法区域一定是一段区间[L,R],且L=mid+1,R单调递增对答案的贡献为这里写图片描述这里写图片描述维护这一个区间就可以了
2、 Min[i]>Min[j]且Max[i]<Max[j]
与第一种情况类似,但是L不一定是mid+1,R=r对答案的贡献为 这里写图片描述 =这里写图片描述 同理在维护区间时维护这两个和就行了
3、 Max[i]>=Max[j]且Min[i]>Min[j]还是从大到小枚举i,j的合法区域依然是一个区间[L,R],但是L,R都是∈[mid+1,r]的,这时需要维护的是一个类似队列的,不再是栈对答案的贡献是 这里写图片描述 =这里写图片描述 同样的方法也可以维护,就是需要队列的头维护
4、 Max[i]<Max[j]且Min[i]<=Min[j]与3、的方法一模一样,就是Max和Min反一下。解决了这个问题,就可以顺利的分治了,T(n)=2T(n/2)+Θ(n)= Θ(n lg n)

code:

#include<cstdio>
#include<algorithm>
const int p=1000000007;
inline int mo(int x){
    if(x>=p)
        return x-p;
    return x;
}
int n,a[500005],ans,Max[500005],Min[500005],sum,last,sum1,head,tail;
void solve(int l,int r){
    if(l==r){
        ans=mo(ans+(long long)a[l]*(long long)a[l]%p);
        return;
    }
    int mid=(l+r)>>1;
    Max[mid]=Min[mid]=a[mid];
    for(int i=mid-1;i>=l;i--){
        Max[i]=std::max(Max[i+1],a[i]);
        Min[i]=std::min(Min[i+1],a[i]);
    }
    Max[mid+1]=Min[mid+1]=a[mid+1];
    for(int i=mid+2;i<=r;i++){
        Max[i]=std::max(Max[i-1],a[i]);
        Min[i]=std::min(Min[i-1],a[i]);
    }
    last=mid+1;
    sum=0;
    for(int i=mid;i>=l;i--){
        while(last<=r&&Min[last]>=Min[i]&&Max[last]<=Max[i]){
            last++;
            sum=mo(sum+last);
        }
        ans=mo(ans+(long long)Max[i]*(long long)mo(sum-(long long)i*(long long)(last-1-mid)%p+p)%p*Min[i]%p);
    }
    last=r+1;
    sum=sum1=0;
    for(int i=l;i<=mid;i++){
        while(last>mid+1&&Min[last-1]<Min[i]&&Max[last-1]>Max[i]){
            last--;
            sum=mo(sum+(long long)Max[last]*(long long)Min[last]%p*(long long)(last+1)%p);
            sum1=mo(sum1+(long long)Max[last]*(long long)Min[last]%p);
        }
        ans=mo((ans+sum-(long long)sum1*(long long)i%p)%p+p);
    }
    sum=sum1=0;
    head=mid+1;
    tail=mid;
    for(int i=mid;i>=l;i--){
        while(tail<r&&Max[i]>=Max[tail+1]){
            tail++;
            sum=mo(sum+(long long)Min[tail]*(long long)(tail+1)%p);
            sum1=mo(sum1+Min[tail]);
        }
        while(head<=tail&&Min[i]<=Min[head]){
            sum=mo(sum-(long long)Min[head]*(long long)(head+1)%p+p);
            sum1=mo(sum1-Min[head]+p);
            head++;
        }
        ans=mo(ans+(long long)Max[i]*(long long)(sum-(long long)i*(long long)sum1%p+p)%p);
    }
    sum=sum1=0;
    head=mid+1;
    tail=mid;
    for(int i=mid;i>=l;i--){
        while(tail<r&&Min[i]<=Min[tail+1]){
            tail++;
            sum=mo(sum+(long long)Max[tail]*(long long)(tail+1)%p);
            sum1=mo(sum1+Max[tail]);
        }
        while(head<=tail&&Max[i]>=Max[head]){
            sum=mo(sum-(long long)Max[head]*(long long)(head+1)%p+p);
            sum1=mo(sum1-Max[head]+p);
            head++;
        }
        ans=mo(ans+(long long)Min[i]*(long long)(sum-(long long)i*(long long)sum1%p+p)%p);
    }
    solve(l,mid);
    solve(mid+1,r);
}
int main(){
    freopen("dance.in","r",stdin);
    freopen("dance.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    solve(1,n);
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值