Codeforces Contest 1136 problem E Nastya Hasn't Written a Legend —— 线段树+前缀和的前缀和

In this task, Nastya asked us to write a formal statement.

An array a of length n and an array k of length n−1 are given. Two types of queries should be processed:

increase ai by x. Then if ai+1<ai+ki, ai+1 becomes exactly ai+ki; again, if ai+2<ai+1+ki+1, ai+2 becomes exactly ai+1+ki+1, and so far for ai+3, …, an;
print the sum of the contiguous subarray from the l-th element to the r-th element of the array a.
It’s guaranteed that initially ai+ki≤ai+1 for all 1≤i≤n−1.

Input
The first line contains a single integer n (2≤n≤105) — the number of elements in the array a.

The second line contains n integers a1,a2,…,an (−109≤ai≤109) — the elements of the array a.

The third line contains n−1 integers k1,k2,…,kn−1 (−106≤ki≤106) — the elements of the array k.

The fourth line contains a single integer q (1≤q≤105) — the number of queries.

Each of the following q lines contains a query of one of two types:

if the query has the first type, the corresponding line contains the character ‘+’ (without quotes), and then there are two integers i and x (1≤i≤n, 0≤x≤106), it means that integer x is added to the i-th element of the array a as described in the statement.
if the query has the second type, the corresponding line contains the character ‘s’ (without quotes) and then there are two integers l and r (1≤l≤r≤n).
Output
For each query of the second type print a single integer in a new line — the sum of the corresponding subarray.

Examples
inputCopy
3
1 2 3
1 -1
5
s 2 3

  • 1 2
    s 1 2
  • 3 1
    s 2 3
    outputCopy
    5
    7
    8
    inputCopy
    3
    3 6 7
    3 1
    3
  • 1 3
  • 2 4
    s 1 3
    outputCopy
    33
    Note
    In the first example:

after the first change a=[3,4,3];
after the second change a=[3,4,4].
In the second example:

after the first change a=[6,9,10];
after the second change a=[6,13,14].

题意:

给你两个数组,第一个数组是n个值,第二个数组是改变量,什么叫改变量呢,当我增加第一个数组的x位置的值y的时候,如果a[x]+y+k[x]>a[x+1],那么a[x+1]=a[x]+y+k[x]。一直比到最后。还有一个操作是问你l到r区间值的和。一开始默认所有a[x]+k[x]<a[x+1]

题解:

非常好的题目,我想了很久都做不出来。当看到网上题解写二分的时候,茅厕顿开呀。因为到一个点a[x]>a[x-1]+前缀的时候,那么之后的就也无法更新,因为a[x]状态不变的话,后面状态是不会改变的,满足二分的性质,所以我们二分更新到哪里结束即可。之后就是怎么更新的问题,线段树维护的是区间和,那么对于更新某个位置x,我们知道a[x+1]=a[x]+y+k[x],a[x+2]=a[x]+y+k[x]+k[x+1]。这样的话我们用一个前缀和数组p来记录k的前缀和,之后,对于这个区间l到r,我们可以发现k[l]加了(r-l+1)次,k[l+1]加了(r-l)次,同样的,我们就可以用一个前缀和数组pp来表示p数组的前缀和,这样就能得到k[i]加了(n-i+1)次的和。那么我们更新的时候就是pp[r]-pp[l-1]+(a[x]+y)(r-l+1)-p[x]*(r-l+1);
因为pp[r]-pp[l-1]表示的是从l到r这个区间,从1~l,到1~r的前缀和的和,但是我们计算前缀和当然是要从x开始,所以我们要减掉(r-l+1)个1~x的前缀和,也就是-p[x]
(r-l+1)。但是我们怎么维护x?可能他会被push_down,如果用一个标记记录的话,下一次就不知道x到底是哪个位置了,但是我们发现a[x]+y与p[x]的系数都是一样的,所以我们在更新的时候直接合并a[x]+y-p[x],记为val,那么这个区间的和就是pp[r]-pp[l-1]+(r-l+1)*val。
最后一点要注意的是,延迟更新标记不能用+=,因为这道题特殊点在于它并不是+=,对于一个点如果两次都被更新,那么就以后一次更新为准。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5;
const ll inf=1e18;
ll a[N],p[N],pp[N],sum[N*4],flag[N*4],k[N];
void build(int l,int r,int root)
{
    flag[root]=-inf;
    if(l==r)
    {
        sum[root]=a[l];
        return ;
    }
    int mid=l+r>>1;
    build(l,mid,root<<1);
    build(mid+1,r,root<<1|1);
    sum[root]=sum[root<<1]+sum[root<<1|1];
}
void push_down(ll l,ll r,int root)
{
    if(flag[root]==-inf)
        return ;
    ll mid=l+r>>1;
    sum[root<<1]=flag[root]*(mid-l+1)+pp[mid]-pp[l-1];
    sum[root<<1|1]=flag[root]*(r-mid)+pp[r]-pp[mid];
    flag[root<<1]=flag[root];
    flag[root<<1|1]=flag[root];
    flag[root]=-inf;
}
void update(ll l,ll r,ll root,ll ql,ll qr,ll val)
{
    if(l>=ql&&r<=qr)
    {
        sum[root]=pp[r]-pp[l-1]+val*(r-l+1);
        flag[root]=val;
        return ;
    }
    push_down(l,r,root);
    int mid=l+r>>1;
    if(mid>=ql)
        update(l,mid,root<<1,ql,qr,val);
    if(mid<qr)
        update(mid+1,r,root<<1|1,ql,qr,val);
    sum[root]=sum[root<<1]+sum[root<<1|1];
}
ll query(int l,int r,int root,int ql,int qr)
{
    if(l>=ql&&r<=qr)
        return sum[root];
    push_down(l,r,root);
    int mid=l+r>>1;
    ll ans=0;
    if(mid>=ql)
        ans=query(l,mid,root<<1,ql,qr);
    if(mid<qr)
        ans+=query(mid+1,r,root<<1|1,ql,qr);
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    build(1,n,1);
    for(int i=2;i<=n;i++)
        scanf("%lld",&k[i]),p[i]=p[i-1]+k[i];
    for(int i=2;i<=n;i++)
        pp[i]=pp[i-1]+p[i];
    int m;
    scanf("%d",&m);
    while(m--)
    {
        char op[2];
        int x,y;
        scanf("%s%d%d",op,&x,&y);
        if(op[0]=='s')
            printf("%lld\n",query(1,n,1,x,y));
        else
        {
            int l=x,r=n,ans=l,mid;
            ll aa=query(1,n,1,x,x);
            while(r>=l)
            {
                mid=l+r>>1;
                ll amid=query(1,n,1,mid,mid);
                if(amid<aa+y+p[mid]-p[x])
                    l=mid+1,ans=mid;
                else
                    r=mid-1;
            }
            update(1,n,1,x,ans,aa+y-p[x]);
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值