1. 程式人生 > >Codeforces 1136E - Nastya Hasn't Written a Legend - [線段樹+二分]

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

with nas lse oid build sim href fine ==

題目鏈接: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;
        }
    }
}

有一個註意點是懶標記初始化成負無窮。

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