BZOJ题目传送门php
表示蒟蒻并不能一眼看出来这是个势能线段树。c++
不过仔细想一想也并不是难以理解,感性理解一下,在一个区间里又与又或,那么原本不相同的位也会渐渐相同,线段树每一个叶子节点最多修改\(\log a\)次(\(a\)为值域)。数组
那么,咱们作区间修改的时候,进行判断:若是这一次修改对区间里全部数的影响都是同样的,那么直接在当前位置放懒标记。ui
如何判断呢?又是一个位运算技巧:维护区间与和区间或,二者的异或即为区间内存在不一样的位集。那么只有这些位集不会被与上0、或上1,才能够放懒标记。spa
至于又与又或很麻烦,咱们定义标记\((la,lo)\)表示整个区间都&la
再|lo
。code
标记的合并手推一下就行了,\((la,lo)+(na,no)=(la\&na,lo\&na|no)\)ip
复杂度\(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; }