Codeforces 1136E - Nastya Hasn't Written a Legend - [线段树+二分]

题目链接:https://codeforces.com/problemset/problem/1136/E

 

题意:

给出一个 $a[1 \sim n]$,以及一个 $k[1 \sim (n-1)]$,初始保证所有的 $1 \le i \le n-1$ 都满足 $a[i]+k[i] \le a[i+1]$。

现在有两种操作:

  第一种是令指定的 $a[i]$ 加上一个非负整数 $x$,此时若有 $a[i] + k[i] > a[i+1]$,则 $a[i+1]$ 变为 $a[i] + k[i]$,往后依次类推。

  第二种是给定一个区间 $[l,r]$ 求 $a[l] + \cdots + a[r]$。

 

题解:

首先,我们知道对一个 $a[i]$ 加上 $x$ 后,根据其后面的 $a[i] + k[i] \le a[i+1], a[i+1] + k[i+1] \le a[i+2], \cdots$ 的“松紧程度”的变化,$a[i]$ 加上 $x$ 其带来的影响会逐步减弱,直到在某个位置 $j$ 之后完全消失,这个 $a[j]$ 是最后一个要被修改的数。

那么,如果我们找到了这个区间 $[i,j]$,我们现在要做的修改操作,就是要对这个区间进行一定的修改。

不难发现,这个区间内的第一个数变成了 $a[i]+x$,第二个变成了 $a[i]+x+k[i]$,第三个变成了 $a[i]+x+k[i]+k[i+1]$,依次类推……

考虑这个式子可以分为两部分:$a[i] + x$ 部分,以及 $k[i] + k[i+1] + \cdots$ 部分。

可以考虑分开维护这两个部分,前一部分很好维护,线段树区间更新;后一部分直接维护比较困难,我们可以这样维护:

$k[i],k[i]+k[i+1],k[i]+k[i+1]+k[i+2],\cdots$,不难看出是一个类似于 $k$ 数组的前缀和的求和,举个栗子:

$k_3 ,\: k_3+k_4 ,\: k_3+k_4+k_5 ,\: k_3+k_4+k_5+k_6$,如果是前缀和求和,那么应当是 $k_1+k_2+k_3 \:,\: k_1+k_2+k_3+k_4 \:,\: k_1+k_2+k_3+k_4+k_5 \:,\: k_1+k_2+k_3+k_4+k_5+k_6$。

也就是说,要在前缀和上减掉 $4 \times (k_1+k_2)$,不难发现,这个值是比较好维护的,是对某一段区间直接赋值,所以可以用线段树维护这个东西。

 

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18;
const int maxn=1e5+10;
int n,q;
ll a[maxn],k[maxn],s[maxn];

struct Node
{
    int l,r;
    ll val,lazy;
    void update(ll x)
    {
        val=(r-l+1)*x;
        lazy=x;
    }
};
struct SegmentTree
{
#define ls (rt<<1)
#define rs (rt<<1|1)
    Node o[maxn<<2];
    void pushdown(int rt)
    {
        if(o[rt].lazy!=-INF)
        {
            o[ls].update(o[rt].lazy);
            o[rs].update(o[rt].lazy);
            o[rt].lazy=-INF;
        }
    }
    void pushup(int rt)
    {
        o[rt].val=o[ls].val+o[rs].val;
    }
    void build(int rt,int l,int r,ll v[])
    {
        o[rt].l=l, o[rt].r=r;
        o[rt].lazy=-INF;
        if(l==r)
        {
            o[rt].val=v[l];
            return;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid,v);
        build(rs,mid+1,r,v);
        pushup(rt);
    }
    void update(int rt,int st,int ed,ll val)
    {
        if(st<=o[rt].l && o[rt].r<=ed)
        {
            o[rt].update(val);
            return;
        }
        pushdown(rt);
        int mid=(o[rt].l+o[rt].r)>>1;
        if(st<=mid) update(ls,st,ed,val);
        if(mid<ed) update(rs,st,ed,val);
        pushup(rt);
    }
    ll query(int rt,int st,int ed)
    {
        if(st<=o[rt].l && o[rt].r<=ed) return o[rt].val;
        pushdown(rt);
        ll res=0;
        int mid=(o[rt].l+o[rt].r)>>1;
        if(st<=mid) res+=query(ls,st,ed);
        if(mid<ed) res+=query(rs,st,ed);
        return res;
    }
}T[2];

inline ll getval(int p)
{
    return T[0].query(1,p,p)+k[p]-T[1].query(1,p,p);
}
void modify(int p,ll x)
{
    int l=p, r=n;
    ll base=getval(p);
    while(l<r)
    {
        int mid=(l+r+1)>>1;
        if(base+x+k[mid]-k[p]>getval(mid)) l=mid;
        else r=mid-1;
    }
    T[0].update(1,p,r,base+x);
    T[1].update(1,p,r,k[p]);
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];

    k[1]=s[1]=0;
    for(int i=2;i<=n;i++) cin>>k[i], k[i]+=k[i-1], s[i]=s[i-1]+k[i];

    T[0].build(1,1,n,a);
    T[1].build(1,1,n,k);

    cin>>q;
    char op[2];
    while(q--)
    {
        cin>>op;
        if(op[0]=='+')
        {
            int p; ll x; cin>>p>>x;
            modify(p,x);
        }
        if(op[0]=='s')
        {
            int l,r; cin>>l>>r;
            ll res1=T[0].query(1,l,r);
            ll res2=s[r]-s[l-1]-T[1].query(1,l,r);
            cout<<res1+res2<<'\n';
        }
    }
}

有一个注意点是懒标记初始化成负无穷。

posted @ 2019-03-24 10:14  Dilthey  阅读(307)  评论(0编辑  收藏  举报