BZOJ5312 冒险(势能线段树)

BZOJ题目传送门

表示蒟蒻并不能一眼看出来这是个势能线段树。

不过仔细想想也并非难以理解,感性理解一下,在一个区间里又与又或,那么本来不相同的位也会渐渐相同,线段树每个叶子节点最多修改\(\log a\)次(\(a\)为值域)。

那么,我们做区间修改的时候,进行判断:如果这一次修改对区间里所有数的影响都是一样的,那么直接在当前位置放懒标记。

如何判断呢?又是一个位运算技巧:维护区间与和区间或,两者的异或即为区间内存在不同的位集。那么只有这些位集不会被与上0、或上1,才可以放懒标记。

至于又与又或很麻烦,我们定义标记\((la,lo)\)表示整个区间都&la|lo

标记的合并手推一下就好了,\((la,lo)+(na,no)=(la\&na,lo\&na|no)\)

复杂度\(n\log n\log a\),果然维护的东西一多数组版线段树的常数就大起来了。。。

#include<bits/stdc++.h>
#define RG register
#define R RG int
#define G if(++ip==ie)fread(ip=buf,1,N,stdin)
using namespace std;
const int N=1<<19,S=(1<<21)-1;
char buf[N],*ie=buf+N,*ip=ie-1;
int na,no,sa[N],so[N],la[N],lo[N],mx[N];
inline int in(){
    G;while(*ip<'-')G;
    R x=*ip&15;G;
    while(*ip>'-'){x*=10;x+=*ip&15;G;}
    return x;
}
#define Pushup                                  \
    sa[x]=sa[lc]&sa[rc];                        \
    so[x]=so[lc]|so[rc];                        \
    mx[x]=max(mx[lc],mx[rc])
#define Pushdn                                  \
    if(la[x]!=S||lo[x]){                        \
        pusht(lc,la[x],lo[x]);                  \
        pusht(rc,la[x],lo[x]);                  \
        la[x]=S;lo[x]=0;                        \
    }
inline void pusht(R x,R a,R o){//合并标记并更新信息
    la[x]&=a;(lo[x]&=a)|=o;
    (so[x]&=a)|=o;(sa[x]&=a)|=o;(mx[x]&=a)|=o;
}
void build(R x,R l,R r){
    la[x]=S;
    if(l==r){
        mx[x]=sa[x]=so[x]=in();return;
    }
    R m=(l+r)>>1,lc=x<<1,rc=lc|1;
    build(lc,l,m);build(rc,m+1,r);
    Pushup;
}
void upd(R x,R l,R r,R s,R e){
    if(l==s&&r==e&&!((sa[x]^so[x])&(~na|no)))//判断是否影响一致
        return pusht(x,na,no);
    R m=(l+r)>>1,lc=x<<1,rc=lc|1;
    Pushdn;
    if(e<=m)upd(lc,l,m,s,e);
    else if(s>m)upd(rc,m+1,r,s,e);
    else upd(lc,l,m,s,m),upd(rc,m+1,r,m+1,e);
    Pushup;
}
int qry(R x,R l,R r,R s,R e){
    if(l==s&&r==e)return mx[x];
    R m=(l+r)>>1,lc=x<<1,rc=lc|1;
    Pushdn;
    if(e<=m)return qry(lc,l,m,s,e);
    if(s>m)return qry(rc,m+1,r,s,e);
    return max(qry(lc,l,m,s,m),qry(rc,m+1,r,m+1,e));
}
int main(){
    R n=in(),m=in(),op,l,r;
    build(1,1,n);
    while(m--){
        op=in();l=in();r=in();
        if(op==1)na=in(),no=0,upd(1,1,n,l,r);//或上0还是原来的数
        if(op==2)na=S,no=in(),upd(1,1,n,l,r);//与上全1还是原来的数
        if(op==3)printf("%d\n",qry(1,1,n,l,r));
    }
    return 0;
}

转载于:https://www.cnblogs.com/flashhu/p/9686599.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值