一类分治问题

有一类关于区间最大值和最小值之类的问题,利用单调性,可以采用分治算法解决。

SPOJ22343 Norma

题意,给定一个数列,定义区间的代价为区间最大值、区间最小值、区间长度的乘积,求所有区间的代价和。

既然是分治,我们肯定要处理一个数列跨过中点的答案。

假设当前数列的中点为mid,我们从mid往前扫,扫到了i。

然后根据单调性,我们越往左扫,最大值单调不降,最小值单调不增。

那么我们可以在右边维护一个指针,表示满足最大值的区间的最靠右的端点。

假设有这么一种情况,那么我们可以把区间拆成mid~p1,p1~p2,p2~r,三段。

1、mid~p1,max和min都是已知的,max*min*(r-l+1)。

2、p1~p2,min是已知的,我们需要算一个sigma(max)再乘上区间长度搞个前缀和算一下。

3、maxmin都未知,我们还需要维护max*min*区间长度搞成前缀和。

Code

#include<iostream>
#include<cstdio>
#define N 500010
#define inf 1e18
using namespace std;
typedef long long ll;
const int mod=1e9;
ll ans,a[N],mn[N][2],mi[N][2],ji[N][2];
int n;
inline ll calc(ll l,ll r){
    return ((l+r)*(r-l+1)/2)%mod;
}
void solve(int l,int r){
    if(l==r){(ans+=a[l]*a[l]%mod)%=mod;return;}
    ll mid=(l+r)>>1;
    solve(l,mid);solve(mid+1,r); 
    ll maxn=-inf,minn=inf;
    mn[mid][0]=mn[mid][1]=mi[mid][0]=mi[mid][1]=ji[mid][0]=ji[mid][1]=0;
    for(ll i=mid+1;i<=r;++i){
        maxn=max(maxn,a[i]);minn=min(minn,a[i]);
        mn[i][0]=(mn[i-1][0]+maxn)%mod;mn[i][1]=(mn[i-1][1]+maxn*(i-mid)%mod)%mod;
        mi[i][0]=(mi[i-1][0]+minn)%mod;mi[i][1]=(mi[i-1][1]+minn*(i-mid)%mod)%mod;
        ji[i][0]=(ji[i-1][0]+minn*maxn%mod)%mod;ji[i][1]=(ji[i-1][1]+minn*maxn%mod*(i-mid)%mod)%mod;
    }
    ll p=mid+1,q=mid+1;maxn=-inf;minn=inf;
    for(ll i=mid;i>=l;--i){
        minn=min(minn,a[i]);maxn=max(maxn,a[i]);
        while(p<=r&&a[p]>=minn)p++;
        while(q<=r&&a[q]<=maxn)q++;
        int ls=min(p,q),rs=max(p,q);
        (ans+=maxn*minn%mod*calc(mid+1-i+1,ls-i)%mod)%=mod;
        if(p<q)ans=(ans+((mi[rs-1][0]-mi[ls-1][0])*(mid-i+1)%mod+mi[rs-1][1]-mi[ls-1][1])%mod*maxn%mod+mod)%mod;
        if(p>q)ans=(ans+((mn[rs-1][0]-mn[ls-1][0])*(mid-i+1)%mod+mn[rs-1][1]-mn[ls-1][1])%mod*minn%mod+mod)%mod; 
        ans=((ans+(ji[r][0]-ji[rs-1][0])*(mid-i+1)%mod+(ji[r][1]-ji[rs-1][1])%mod)%mod+mod)%mod;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    solve(1,n); 
    cout<<ans;
    return 0;
} 

CF526F

化简题意,给定一个排列,求有多少区间满足max-min=r-l;

和上题一样,考虑跨过中点的答案,因为我们要计算合法区间,所以这和max和min的分布情况有关。

最大值最小值都在一侧的情况,max,min,l都已知,r是可以O(1)求出的,直接判断就好了。

如果最大值最小值分布在两侧,我们可以在一边枚举最端点,在右边用双指针维护一个区间满足最大值在左边,最小值在右边。

然后max-min=r-l  ->  max+l=r+min用桶维护这个式子。

最大值最小值在右边,最小值在左边最大值在右边的情况同理。

Code

#include<iostream>
#include<cstdio>
#define N 300002
#define inf 0x3f3f3f3f
using namespace std;
int ma[N],mi[N],a[N],l,r,tong[N<<1],n;
long long ans;
void solve(int l,int r){
    if(l==r){ans++;return;}
    int mid=(l+r)>>1;
    solve(l,mid);solve(mid+1,r);
    ma[mid]=0;mi[mid]=inf;
    for(int i=mid+1;i<=r;++i){
        ma[i]=max(ma[i-1],a[i]);
        mi[i]=min(mi[i-1],a[i]);
    }
    ma[mid]=mi[mid]=a[mid];
    for(int i=mid-1;i>=l;--i){
        ma[i]=max(ma[i+1],a[i]);
        mi[i]=min(mi[i+1],a[i]);
    }
    int p=mid+1,q;
    for(int i=mid;i>=l;--i){
        p=ma[i]-mi[i]+i;
        if(p<=r&&p>=mid+1&&ma[p]<ma[i]&&mi[p]>mi[i])ans++;
    }
    p=mid;
    for(int i=mid+1;i<=r;++i){
        p=i-ma[i]+mi[i];
        if(p>=l&&p<=mid&&ma[p]<ma[i]&&mi[p]>mi[i])ans++;
    } 
    p=q=mid;
    for(int i=mid+1;i<=r;++i){
        while(ma[p]<ma[i]&&p>=l)tong[mi[p]-p+n]++,p--;
        while(mi[q]>mi[i]&&q>p)tong[mi[q]-q+n]--,q--;
        ans+=tong[ma[i]-i+n];
    }
    while(q>p)tong[mi[q]-q+n]--,q--;
    p=q=mid+1;
    for(int i=mid;i>=l;--i){
        while(ma[p]<ma[i]&&p<=r)tong[mi[p]+p]++,p++;
        while(mi[q]>mi[i]&&q<p)tong[mi[q]+q]--,q++;
        ans+=tong[ma[i]+i];
    }
    while(q<p)tong[mi[q]+q]--,q++;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d%d",&l,&r);
        a[l]=r;
    }
    solve(1,n);
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/ZH-comld/p/9880982.html