P5445 [APIO2019] 路灯 题解
题目描述
给你一个 01 串,有 \(q\) 个时刻,每个时刻要么把一位取反,要么问你在过去的所有时刻中有多少个时刻 \(a\) 和 \(b-1\) 之间都为 1。
题目分析
观察题目,我们会发现可以把全为 1 的段看做一个连通块,如果两个位置在一个块内则可以互相到达,修改某个位置的值就相当于把两边的连通块合并或者分裂。
但是我们此时并非维护一个动态的连通块,而是需要知道所有时刻的信息,但是如果又把所有时刻遍历一遍会超时,考虑能不能用空间换时间,储存下所有时刻的信息方便维护。
容易观察到,维护一个连通块的目的无非是为了检查某两个点联不联通,那么我们可以抛弃连通块,转而维护两个点联通的时间数,这看似有些暴力,毕竟从空间复杂度上看 \(n^2\) 规模就已经超标了,别急,让我们先看看题目怎么操作。
对于一个询问操作自然没什么好说的,那么对于修改操作则如先前所述是将两边连通块分裂或者合并,我们把操作更改一下,记 \(l_1\),\(r_1\),\(l_2\),\(r_2\) 分别为两边连通块的左右端点,则合并操作表示所有左端点在 \([l_1,r_1]\) 内,右端点在 \([l_2,r_2]\) 的点对以后都联通,分裂相反。
注意到受影响的点实际上在平面内构成一个矩形,而询问相当于单点求值,我们能不能把修改转化成对于矩形的修改呢?当然可以,我们把对于一个点实际有效的时间段抽出来看,它实际上可以差分成一次单点加和单点减。
记当前时刻为 \(t\),只要合并时把整个矩形加上 \(q-t\),分裂时减去 \(q-t\) 即可,差分后可以使用 cdq 分治或者树套树解决,另外要注意的是查询时如果还联通,由于不考虑以后的时间,要将答案减去 \(q-t\)。
那么左右联通块如何维护呢,其实很简单,要么模仿珂朵莉树用 set 维护,要么用一颗线段树维护查询时二分即可,笔者这里使用了线段树的写法,不过细节多而且复杂度较高,还是建议使用 set 维护。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<cstring>
#define ite set<pai>::iterator
#define N 300005
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int tot,num,sta[N];
int a[N],rt[N];
int n,q;
struct Node{
int ls,rs,sum;
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define s(x) tr[x].sum
}tr[N<<8];
int query(int x,int l,int r,int L,int R){
if(!x) return 0;
if(L>=l && R<=r)return s(x);
int mid=(L+R)>>1,ans=0;
if(l<=mid) ans+=query(ls(x),l,r,L,mid);
if(r>mid) ans+=query(rs(x),l,r,mid+1,R);
return ans;
}
void change(int &x,int p,int L,int R,int s){
if(!x) x=++tot;
s(x)+=s;if(L==R) return;
int mid=(L+R)>>1;
if(p<=mid) change(ls(x),p,L,mid,s);
else change(rs(x),p,mid+1,R,s);
}
int lowbit(int x){
return x&(-x);
}
int ask(int x,int y){
int ans=0;
while(x){
ans+=query(rt[x],1,y,1,n+2);
x-=lowbit(x);
}
return ans;
}
void add(int x,int y,int p){
while(x<=n){
change(rt[x],y,1,n+2,p);
x+=lowbit(x);
}
}
struct no{
int ls,rs,sum,l,r;
#define tls(x) t2[x].ls
#define trs(x) t2[x].rs
#define ts(x) t2[x].sum
#define tl(x) t2[x].l
#define tr(x) t2[x].r
}t2[N<<2];
void build(int &x,int l,int r){
x=++num;tl(x)=l;tr(x)=r;
if(l==r){ts(x)=sta[l];return;}
int mid=(l+r)>>1;
build(tls(x),l,mid);build(trs(x),mid+1,r);
ts(x)=ts(tls(x))+ts(trs(x));
}
void ct(int x,int p){
if(tl(x)==tr(x)) {ts(x)=sta[tl(x)];return;}
int mid=(tl(x)+tr(x))>>1;
if(p<=mid) ct(tls(x),p);
else ct(trs(x),p);
ts(x)=ts(tls(x))+ts(trs(x));
}
int qt(int x,int l,int r){
if(tl(x)>=l && tr(x)<=r) return ts(x);
int mid=(tl(x)+tr(x))>>1;int ans=0;
if(l<=mid) ans+=qt(tls(x),l,r);
if(r>mid) ans+=qt(trs(x),l,r);
return ans;
}
int findr(int x){
if(x==n) return n+1;
if(qt(1,x+1,x+1)==0) return x+1;
int l=x+1,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(qt(1,x+1,mid)==mid-x){
l=mid;
}
else r=mid-1;
}
return l+1;
}
int findl(int x){
if(x==1) return 1;
if(qt(1,x-1,x-1)==0){
return x;
}
int l=1,r=x-1;
while(l<r){
int mid=(l+r)>>1;
if(qt(1,mid,x-1)==x-1-mid+1) r=mid;
else l=mid+1;
}
return l;
}
void opti(int l,int r,int ll,int rr,int p){
add(l,r,p);add(ll+1,r,-p);add(l,rr+1,-p);add(ll+1,rr+1,p);
}
int main(){
n=read();q=read();int l,r;char ch;string s;
for(int i=1;i<=n;i++){cin>>ch;sta[i]=ch-'0';}
build(l,1,n);int st=0;
for(int i=1;i<=n;i++){
if(sta[i]==1 && sta[i-1]==0) st=i;
if(sta[i]==1 && sta[i+1]==0){
opti(st,st,i+1,i+1,q);
}
}
for(int i=1;i<=q;i++){
cin>>s;
if(s=="query"){
l=read();r=read();if(l==r){cout<<i<<endl;continue;}
int ans=ask(l,r);
if(qt(1,l,r-1)==(r-1-l+1)) ans+=i-q;
cout<<ans<<endl;
}
else{
l=read();
int ll=findl(l);
int rr=findr(l);
if(sta[l]==0)opti(ll,l+1,l,rr,q-i);
else opti(ll,l+1,l,rr,i-q);
sta[l]^=1;ct(1,l);
}
}
}