巫师的总力量和【单调栈+前缀和的前缀和】

搞懂这个题需要先搞懂P1856子数组最小乘积的最大值,关于P1856和单调栈的知识可以见上一篇关于单调栈的文章寻找下一个最大/小问题【单调栈】
此题目是leetcode294场周赛T4,题目在巫师的总力量和在这里插入图片描述

  • 分析题目,相比于枚举区间,我们更倾向于枚举每一个数字,然后求以他为最小值的区间的总力量值。为了知道遍历到 i i i时左右端点的值,我们采用单调栈记录每一个点的左边第一个小于他的位置 l e f t [ i ] left[i] left[i]、右边第一个小于他的位置 r i g h t [ i ] right[i] right[i]。那么需要计算的区间即为 [ l e f t [ i ] + 1 , r i g h t [ i ] − 1 ] [left[i]+1,right[i]-1] [left[i]+1,right[i]1].
  • 在单调栈的实现中,对于位置 i i i,如果他的左边没有比他小的,那么 l e f t [ i ] = − 1 left[i]=-1 left[i]=1,同理,如果右边没有比他小的,那么 r i g h t [ i ] = n right[i]=n right[i]=n
  • 对于数组[1,3,1,2],对于两个1来说,左右都没有严格比1小的数,因此计算两个1时,区间都为左端点到右端点:整个数组;造成了重复计算,为了避免重复计算,可以采取的办法是:左边取严格小于的数,右边取小于等于的数,这样就不会有重复计算的问题,同时也不会影响左右端点的值。
  • 假设当前遍历到的数组的值为 x x x,位置为 i i i,原数组为nums,需要计算的区间即为 [ l e f t [ i ] + 1 , r i g h t [ i ] − 1 ] [left[i]+1,right[i]-1] [left[i]+1,right[i]1]。令 L = l e f t [ i ] + 1 L=left[i]+1 L=left[i]+1 R = r i g h t [ i ] − 1 R=right[i]-1 R=right[i]1,即问题为:对于一个区间[L,R],需要计算所有包含了 x x x的子数组的和
  • 多次计算子数组的和当然是使用前缀和的方法,前缀和数组s,第一项为0,此后为原数组的各项的和,一共n+1项。对于s[b]-s[a],则表示 ∑ i = a b − 1 n u m s [ i ] \sum_{i=a}^{b-1} nums[i] i=ab1nums[i],即从nums[a]累加到nums[b-1]
  • 回到问题,遍历到位置为 i i i的数 x x x,对于区间[L,R],假设其中一个子区间为[l,r],其中l≤i≤r,则此子区间的和为 ∑ i = l r n u m s [ i ] \sum_{i=l}^{r} nums[i] i=lrnums[i],即s[r+1]-s[l]。则[L,R]对整个答案的贡献为: ∑ r = i + 1 R + 1 \sum_{r=i+1}^{R+1} r=i+1R+1 ∑ l = L i ( s [ r ] − s [ l ] ) \sum_{l=L}^{i} (s[r]-s[l]) l=Li(s[r]s[l]) ,即左端点从L到i,右端点从i到R(但是前缀和的计算到R+1,且起始点为i+1)
    r e s = ∑ r = i + 1 R + 1 ∑ l = L i ( s [ r ] − s [ l ] ) = ∑ r = i + 1 R + 1 [ ( i − L + 1 ) ∗ s [ r ] − ∑ l = L i s [ l ] ] = ( i − L + 1 ) ∗ ∑ r = i + 1 R + 1 s [ r ] − ∑ r = i + 1 R + 1 ∑ l = L i s [ l ] = ( i − L + 1 ) ∗ ∑ r = i + 1 R + 1 s [ r ] − ( R − i + 1 ) ∗ ∑ l = L i s [ l ] \begin{aligned} res&=\sum_{r=i+1}^{R+1}\sum_{l=L}^{i} (s[r]-s[l])&\\ &=\sum_{r=i+1}^{R+1} [(i-L+1)*s[r]-\sum_{l=L}^{i} s[l]]\\ &=(i-L+1)*\sum_{r=i+1}^{R+1} s[r]-\sum_{r=i+1}^{R+1}\sum_{l=L}^{i} s[l]\\ &=(i-L+1)*\sum_{r=i+1}^{R+1} s[r]-(R-i+1)*\sum_{l=L}^{i} s[l]\\ \end{aligned} res=r=i+1R+1l=Li(s[r]s[l])=r=i+1R+1[(iL+1)s[r]l=Lis[l]]=(iL+1)r=i+1R+1s[r]r=i+1R+1l=Lis[l]=(iL+1)r=i+1R+1s[r](Ri+1)l=Lis[l]
  • 可以看出,为了求解[L,R]对答案的贡献,还需要累加s数组,可以采取的办法同上,对s数组进行前缀和操作,设数组为ss,即原数组nums的前缀和的前缀和,第一项为0,此后为s数组的各项的和,一共n+2项。对于ss[b]-ss[a],则表示 ∑ i = a b − 1 s [ i ] \sum_{i=a}^{b-1} s[i] i=ab1s[i],即从s[a]累加到s[b-1]
  • 因此 ∑ r = i + 1 R + 1 s [ r ] \sum_{r=i+1}^{R+1} s[r] r=i+1R+1s[r]= s s [ R + 2 ] − s s [ i + 1 ] ss[R+2]-ss[i+1] ss[R+2]ss[i+1] ∑ l = L i s [ l ] \sum_{l=L}^{i} s[l] l=Lis[l]= s s [ i + 1 ] − s [ L ] ss[i+1]-s[L] ss[i+1]s[L]
  • 因此[L,R]中对答案的贡献为: x ∗ r e s = x ∗ [ ( i − L + 1 ) ∗ ( s s [ R + 2 ] − s s [ i + 1 ] ) − ( R − i + 1 ) ∗ ( s s [ i + 1 ] − s s [ L ] ) ] x*res=x*[(i-L+1)*(ss[R+2]-ss[i+1])-(R-i+1)*(ss[i+1]-ss[L])] xres=x[(iL+1)(ss[R+2]ss[i+1])(Ri+1)(ss[i+1]ss[L])],累加所用贡献即为答案。
    代码如下:
const int MOD=1e9+7;
int totalStrength(vector<int>& strength) {
    int n=strength.size();
    stack<int>s;
    vector<int>left(n),right(n);
    for(int i=0;i<n;i++){ //单调栈求解左边第一个严格小于的位置
        while(!s.empty()&&strength[s.top()]>=strength[i]){
            s.pop();
        }
        left[i]=s.empty()? -1:s.top();
        s.push(i);
    }
    while(!s.empty()) s.pop();
    for(int i=n-1;i>=0;i--){//单调栈求解右边第一个严格小于等于的位置
        while(!s.empty()&&strength[s.top()]>strength[i]){
            s.pop();
        }
        right[i]=s.empty()? n:s.top();
        s.push(i);
    }
    vector<long long>s1(n+1,0); //原数组的前缀和s数组
    vector<long long>ss(n+2,0); //s数组的前缀和ss数组
    for(int i=0;i<n;i++){
        s1[i+1]=(s1[i]+strength[i])%MOD;
    }
    for(int i=0;i<=n;i++){
        ss[i+1]=(s1[i]+ss[i])%MOD;
    }
    long long ans=0;
    for(int i=0;i<n;i++){
        int L=left[i]+1;
        int R=right[i]-1;
        long long res=((i-L+1)*(ss[R+2]-ss[i+1])-(R-i+1)*(ss[i+1]-ss[L]))%MOD; //计算res的值
        ans+=strength[i]*res; //计算此区间的所有子区间的贡献值
        ans%=MOD;
    }
    return (int)(ans+MOD)%MOD; //计算res时可能有负数,防止负数溢出
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值