BZOJ3745: [Coci2015]Norma【分治】

题目描述:

在这里插入图片描述
Mod 109

题目分析:

分治真是强大。。

计算 [ l , r ] [l,r] [l,r]的答案,只需要考虑怎么算跨过 m i d mid mid的答案,其余的分治即可。枚举左端点 i : m i d → l i:mid\rarr l i:midl,记 [ i , m i d ] [i,mid] [i,mid]的最小值为 m n mn mn,最大值为 m x mx mx,找到右边最后一个 ≥ m n \ge mn mn的位置 j j j,以及最后一个 ≤ m x \le mx mx的位置 k k k

不妨设 j ≤ k j\le k jk,这时答案分成了三个部分:

  • 右端点 ≤ j \le j j a n s = m n ∗ m x ∑ p = m i d + 1 j p − i + 1 ans=mn*mx\sum_{p=mid+1}^j{p-i+1} ans=mnmxp=mid+1jpi+1,可直接计算。
  • j < j< j<右端点 ≤ k \le k k a n s = m n ∗ ∑ p = j + 1 k m a x [ m i d + 1 , p ] ∗ ( p − i + 1 ) = m n ∗ ∑ p = j + 1 k m a x [ m i d + 1 , p ] ∗ p − m n ∗ ∑ p = j + 1 k ∗ m a x [ m i d + 1 , p ] ∗ ( i − 1 ) ans=mn*\sum_{p=j+1}^kmax[mid+1,p]*(p-i+1)=mn*\sum_{p=j+1}^kmax[mid+1,p]*p-mn*\sum_{p=j+1}^k*max[mid+1,p]*(i-1) ans=mnp=j+1kmax[mid+1,p](pi+1)=mnp=j+1kmax[mid+1,p]pmnp=j+1kmax[mid+1,p](i1)
    预处理 ∑ m a x [ m i d + 1 , p ] ∗ p \sum max[mid+1,p]*p max[mid+1,p]p以及 ∑ m a x [ m i d + 1 , p ] \sum max[mid+1,p] max[mid+1,p]即可。
  • k < k< k<右端点 ≤ r \le r r a n s = ∑ p = k + 1 r m i n [ m i d + 1 , p ] ∗ m a x [ m i d + 1 , p ] ∗ ( p − i + 1 ) ans=\sum_{p=k+1}^rmin[mid+1,p]*max[mid+1,p]*(p-i+1) ans=p=k+1rmin[mid+1,p]max[mid+1,p](pi+1),同上面一样地拆分。
    预处理 ∑ m i n [ m i d + 1 , p ] ∗ m a x [ m i d + 1 , p ] ∗ p \sum min[mid+1,p]*max[mid+1,p]*p min[mid+1,p]max[mid+1,p]p以及 m i n [ m i d + 1 , p ] ∗ m a x [ m i d + 1 , p ] min[mid+1,p]*max[mid+1,p] min[mid+1,p]max[mid+1,p]即可。

随着 i : m i d → l i:mid\rarr l i:midl j j j k k k单调不减,所以复杂度是 O ( r − l ) O(r-l) O(rl)的,总复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn)

如果可以用 O ( r − l ) O(r-l) O(rl) O ( ( r − l ) ∗ l o g ) O((r-l)*log) O((rl)log)的时间,来解决跨越 m i d mid mid的答案,这时就可以考虑分治。

Code:

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define maxn 500005 
using namespace std;
const int mod = 1e9;
int n,a[maxn],ans;
int smn[maxn],SMN[maxn],smx[maxn],SMX[maxn],mnx[maxn],MNX[maxn];
void solve(int l,int r){
    if(l==r) {ans=(ans+1ll*a[l]*a[r])%mod;return;}
    int mid=(l+r)>>1;
    solve(l,mid),solve(mid+1,r);
    smn[mid]=SMN[mid]=smx[mid]=SMX[mid]=mnx[mid]=MNX[mid]=0;
    for(int i=mid+1,mx=0,mn=1e9;i<=r;i++){
        mx=max(mx,a[i]),mn=min(mn,a[i]);
        smn[i]=(smn[i-1]+mn)%mod,SMN[i]=(SMN[i-1]+1ll*mn*i)%mod;
        smx[i]=(smx[i-1]+mx)%mod,SMX[i]=(SMX[i-1]+1ll*mx*i)%mod;
        mnx[i]=(mnx[i-1]+1ll*mn*mx)%mod,MNX[i]=(MNX[i-1]+1ll*mn*mx%mod*i)%mod;
    }
    for(int i=mid,j=mid,k=mid,mx=0,mn=1e9;i>=l;i--){
        mx=max(mx,a[i]),mn=min(mn,a[i]);
        while(j<r&&a[j+1]>=mn) j++;
        while(k<r&&a[k+1]<=mx) k++;
        int x=min(j,k),y=max(j,k);
        ans=(ans+1ll*(mid+1-i+1+x-i+1)*(x-mid)/2%mod*mx%mod*mn)%mod;
        ans=(ans+MNX[r]-MNX[y]-1ll*(mnx[r]-mnx[y])*(i-1))%mod;
        if(j<k) ans=(ans+1ll*mx*(SMN[k]-SMN[j]-1ll*(smn[k]-smn[j])*(i-1)%mod))%mod;
        else ans=(ans+1ll*mn*(SMX[j]-SMX[k]-1ll*(smx[j]-smx[k])*(i-1)%mod))%mod;
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    solve(1,n);
    printf("%d\n",(ans+mod)%mod);
}

还有大佬用线段树的做法。大致思路就是枚举右端点 r : 1 → n r:1\rarr n r:1n,计算左端点的答案,每次移动右端点都会对最小值或最大值进行区间修改,同样要维护 m i n [ l , r ] ∗ m a x [ l , r ] ∗ l min[l,r]*max[l,r]*l min[l,r]max[l,r]l m i n [ l , r ] ∗ m a x [ l , r ] min[l,r]*max[l,r] min[l,r]max[l,r],然后线段树修改,大致思路就是这样。这里有一篇用线段树写的blog,不确定是不是一样的,姑且看一看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值