bzoj 4355: Play with sequence (线段树)

题目描述

传送门

题目大意:维护一个长度为N的序列a,现在有三种操作:
1)给出参数U,V,C,将a[U],a[U+1],…,a[V-1],a[V]都赋值为C。
2)给出参数U,V,C,对于区间[U,V]里的每个数i,将a[i]赋值为max(a[i]+C,0)。
3)给出参数U,V,输出a[U],a[U+1],…,a[V-1],a[V]里值为0的数字个数。

题解

这道题比较麻烦的就是操作二中的max操作。
正常操作的线段树是不容易实现这种操作的,但是WC2015中提到的segment tree beats!可以实现这种区间取min,max的操作。并且证明每次操作的时间复杂度是 O(log2n) 的。
证明如下:
这里写图片描述
这里写图片描述

这道题有一个很关键的信息,刚开始没有用到。就是cover中的C一定是大于等于0的,也就是说序列中的最小值一定不小于0.为啥很关键?如果没有这个限制0可以处在区间中中间的位置,个数非常不好维护。有了这个限制,那么0如果在区间中出现,那么一定是作为区间最小值出现的,那么问题其实就巧妙的转换成了维护区间最小值的个数。

对于操作2,我们把他拆成两个操作,区间add和区间max。对于add,cover标记得处理比较老套。就是如果cover遇add,直接覆盖.add遇cover,直接把增量给cover,add不变。
对于max(v),我们考虑如何修改。如果区间的最小值>=v,那么该操作对区间没有影响直接舍弃。如果区间最小值 < <script type="math/tex" id="MathJax-Element-10"><</script>v < <script type="math/tex" id="MathJax-Element-11"><</script>区间次小值,那么更改区间最小值和区间delta标记(delta标记维护的实际上类似历史max的东西,如果v能更新delta,说明他改变了区间最小值,这时对于子区间的最小值还没有取max。如果接着来了add操作,那么对于子区间的max影响实际上就是delta+add。所以delta会随着add的改变而改变需要动态的维护).如果区间次小值<=v,也就是区间中不止最小值需要改变,还有很多位置需要修改,只好dfs左右子树,说白了就是暴力修改。。。。
注意cover遇delta,直接把delta清掉即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long 
#define N 300005
using namespace std;
const LL inf=1e15;
struct data{
    LL mn,cmn,cnt,zer,cover,add;
    LL delta;
}tr[N*4];
int n,m;
LL val[N];
void update(data &now,data l,data r)
{
    now.mn=min(l.mn,r.mn);
    now.cnt=0;
    if (l.mn==now.mn) now.cnt+=l.cnt;
    if (r.mn==now.mn) now.cnt+=r.cnt;
    now.zer=l.zer+r.zer;
    now.cmn=inf;
    if (l.mn!=now.mn) now.cmn=min(now.cmn,l.mn);
    if (r.mn!=now.mn) now.cmn=min(now.cmn,r.mn);
    now.cmn=min(now.cmn,l.cmn);
    now.cmn=min(now.cmn,r.cmn);
}
void build(int now,int l,int r)
{
    tr[now].cover=inf; tr[now].delta=-inf;
    if (l==r) {
        tr[now].mn=val[l];
        tr[now].cnt=1;
        tr[now].cmn=inf;
        if (tr[now].mn==0) tr[now].zer=tr[now].cnt;
        return;
    }
    int mid=(l+r)/2;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    update(tr[now],tr[now<<1],tr[now<<1|1]);
}
void solve1(int now,int l,int r,LL v)
{
     tr[now].mn+=v;
     if (tr[now].cmn!=inf) tr[now].cmn+=v;
     if (tr[now].cover!=inf) tr[now].cover+=v;
     else tr[now].add+=v;
     if (tr[now].delta!=-inf) tr[now].delta+=v;
}
void solve(int now,int l,int r,LL v)
{
    tr[now].cover=v;
    tr[now].mn=v; tr[now].cnt=r-l+1;
    tr[now].cmn=inf;
    tr[now].add=0; tr[now].delta=-inf;
}
void solve2(int now,int l,int r,LL v)
{
    if (v>tr[now].mn) {
        tr[now].mn=v;
        tr[now].delta=max(v,tr[now].delta);
    }
}
void pushdown(int now,int l,int r)
{
    int mid=(l+r)/2;
    if (tr[now].add) {
        solve1(now<<1,l,mid,tr[now].add);
        solve1(now<<1|1,mid+1,r,tr[now].add);
        tr[now].add=0;
    }
    if (tr[now].cover!=inf) {
        solve(now<<1,l,mid,tr[now].cover);
        solve(now<<1|1,mid+1,r,tr[now].cover);
        tr[now].cover=inf;
    }
    if (tr[now].delta!=-inf) {
        solve2(now<<1,l,mid,tr[now].delta);
        solve2(now<<1|1,mid+1,r,tr[now].delta);
        tr[now].delta=-inf;
    }
}
void cover(int now,int l,int r,int ll,int rr,LL v)
{
    if (ll<=l&&r<=rr) {
        solve(now,l,r,v);
        return;
    }
    int mid=(l+r)/2;
    pushdown(now,l,r);
    if (ll<=mid) cover(now<<1,l,mid,ll,rr,v);
    if (rr>mid) cover(now<<1|1,mid+1,r,ll,rr,v);
    update(tr[now],tr[now<<1],tr[now<<1|1]);
}
void add(int now,int l,int r,int ll,int rr,LL v)
{
    if (ll<=l&&r<=rr) {
        solve1(now,l,r,v);
        return;
    }
    int mid=(l+r)/2;
    pushdown(now,l,r);
    if (ll<=mid) add(now<<1,l,mid,ll,rr,v);
    if (rr>mid) add(now<<1|1,mid+1,r,ll,rr,v);
    update(tr[now],tr[now<<1],tr[now<<1|1]);
}
void mx(int now,int l,int r,int ll,int rr,int v)
{
    if (ll<=l&&r<=rr){
       if (tr[now].mn>=v) return;
       if (tr[now].cmn>v) {
          tr[now].delta=v;
          tr[now].mn=v;
          return;
       }
       int mid=(l+r)/2;
       pushdown(now,l,r);
       mx(now<<1,l,mid,ll,rr,v);
       mx(now<<1|1,mid+1,r,ll,rr,v);
       update(tr[now],tr[now<<1],tr[now<<1|1]);
       return;
    }
    int mid=(l+r)/2;
    pushdown(now,l,r);
    if (ll<=mid) mx(now<<1,l,mid,ll,rr,v);
    if (rr>mid) mx(now<<1|1,mid+1,r,ll,rr,v);
    update(tr[now],tr[now<<1],tr[now<<1|1]);
}
LL query(int now,int l,int r,int ll,int rr)
{
    if (ll<=l&&r<=rr) return tr[now].mn==0?tr[now].cnt:0;
    int mid=(l+r)/2; LL ans=0; 
    pushdown(now,l,r);
    if (ll<=mid) ans+=query(now<<1,l,mid,ll,rr);
    if (rr>mid) ans+=query(now<<1|1,mid+1,r,ll,rr);
    return ans;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%lld",&val[i]);
    build(1,1,n);
    for (int i=1;i<=m;i++){
        int opt,l,r;  LL v;
        scanf("%d%d%d",&opt,&l,&r);
        if (opt==1) scanf("%lld",&v),cover(1,1,n,l,r,v);
        if (opt==2) { scanf("%lld",&v),add(1,1,n,l,r,v);
                    mx(1,1,n,l,r,0);
                    }
        if (opt==3) 
         printf("%lld\n",query(1,1,n,l,r)); 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值