题意
维护一个初始全为1的01序列,完成以下操作:
- 0 l r:[l,r]变为0
- 1 l1 r1 l2 r2:将[l1,r1]中的所有1拿来替换[l2,r2]中的0,多余的1则扔掉,不够就尽量往靠左的区间填(这一条可以看题中给的例子来理解)
- 2 l r:[l,r]中最长的连续0
思路
每一个节点维护6个信息:
struct tree{
int z1,z0,l0,r0,m0;
}tree[N<<3];
总共1的个数(z1),总共0的个数(z0),从左起最长连续0(l0),从右起最长连续0(r0),最长连续0(m0)。其中 l0 与 r0 都是用来辅助求 m0 的。
维护一个懒标记 tag ,存储3种值:-1(啥也不干),0(全变成0),1(全变成1)。
操作1,3都是比较基础的覆盖与查询操作,这里就不在赘述,主要来分析操作2。
我们可以先将操作2分解为以下几个步骤:
- 查询[l1,r1]中1的总数,并用 tmp 存起来(查询操作)
- 将[l1,r1]全变为0(覆盖操作)
- 将[l2,r2]中最多 tmp 个0替换为1,并满足尽量靠左的要求(???)
前两个步骤都没问题,主要讨论步骤3。仔细观察线段树的模版,可以发现线段树总是会先遍历靠左的区间,正好满足尽量靠左的要求。但是此处的操作为填补,并不是覆盖,就会出现区间合法但无法被填满甚至完全不会被填的情况。此处,我们可以将原先作为 update(更新函数) 的成员变量的 v(val) 改为全局变量,并新增函数 maketag 并在 update 函数中调用:
int v;
void maketag(int u,int l,int r){
pd(u,l,r);
if(v==0||tr[u].z0==0)return ;
if(tr[u].z0<=v){v-=tr[u].z0,tag[u]=1;return ;}
maketag(u<<1,l,mid),maketag(u<<1|1,mid+1,r);
pu(u,l,r);
}
void update(int u,int l,int r,int L,int R){
pd(u,l,r);
if(L<=l&&r<=R){
if(v==-1)tag[u]=0;
else maketag(u,l,r);
return ;
}
剩余部分在此处省略...
}
代码中,我们通过判断 tr[u].z0<=v 来看这段区间是否会在本次被填满(全变成1),若被填满,则效果等同于将此区间全覆盖为1,即:将 tag[u] 赋值为1。若无法被填满,则向下遍历儿子,看儿子会不会被填满,以此类推。
而在每次填满后,都要将使用了的部分从 v 中减去,即:v-=tr[u].z0 。并且要先遍历左儿子(u<<1),以满足尽量靠左的要求。最后,也不要忘了在 maketag 中 pushdown(pd)/pushup(pu)。
此时,所有的操作只剩下了覆盖与查询。因此 pushdown(pd) 也就很简单了:
void pd(int u,int l,int r){
if(tag[u]==0)tr[u]={0,r-l+1,r-l+1,r-l+1,r-l+1};
else if(tag[u]==1)tr[u]={r-l+1,0,0,0,0};
if(tag[u]!=-1)tag[u<<1]=tag[u<<1|1]=tag[u];
tag[u]=-1;
}
此做法时间复杂度为 O(nlogn+mlogn),并不像二分做法为 O(nlogn+m(logn)^2)。
完整代码
以下代码省略了前文的maketag与pd部分:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
struct segment{
#define mid ((l+r)>>1)
struct tree{int z1,z0,l0,r0,m0;};
tree tr[N<<3];int n,tag[N<<3];
segment(){memset(tag,-1,sizeof(tag));}
tree hb(tree i,tree j){
return (tree){i.z1+j.z1,i.z0+j.z0,
i.z1?i.l0:i.m0+j.l0,j.z1?j.r0:j.m0+i.r0,
max(max(i.m0,j.m0),i.r0+j.l0)};
}
void pd(int u,int l,int r){...}
void pu(int u,int l,int r){
if(l^r)pd(u<<1,l,mid),pd(u<<1|1,mid+1,r);
tr[u]=hb(tr[u<<1],tr[u<<1|1]);
}
int v;
void maketag(int u,int l,int r){...}
void update(int u,int l,int r,int L,int R){
pd(u,l,r);
if(L<=l&&r<=R){
if(v==-1)tag[u]=0;
else maketag(u,l,r);
return ;
}
if(L<=mid)update(u<<1,l,mid,L,R);
if(R>mid)update(u<<1|1,mid+1,r,L,R);
pu(u,l,r);
}void update(int L,int R,int tv){v=tv,update(1,1,n,L,R);}
tree query(int u,int l,int r,int L,int R,tree ans=tree()){
pd(u,l,r);
if(L<=l&&r<=R)return tr[u];
if(L<=mid)ans=hb(ans,query(u<<1,l,mid,L,R));
if(R>mid)ans=hb(ans,query(u<<1|1,mid+1,r,L,R));
return ans;
}tree query(int L,int R){return query(1,1,n,L,R);}
void build(int u,int l,int r){
if(l==r){tr[u]={1,0,0,0,0};return ;}
build(u<<1,l,mid),build(u<<1|1,mid+1,r),pu(u,l,r);
}
void write(int u,int l,int r){
pd(u,l,r);
if(l==r){cout<<tr[u].z1<<" ";return ;}
write(u<<1,l,mid),write(u<<1|1,mid+1,r);
pu(u,l,r);
}void write(){write(1,1,n);}
}tree;
int n,m,a[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m,tree.n=n,tree.build(1,1,n);
for(int op,l,r,l0,r0;m--;){
cin>>op>>l>>r;
if(op==0){
tree.update(l,r,-1);
}else if(op==1){
int tmp=tree.query(l,r).z1;tree.update(l,r,-1);
cin>>l0>>r0,tree.update(l0,r0,tmp);
}else cout<<tree.query(l,r).m0<<"\n";
// tree.write(),cout<<"\n";
}
return 0;
}