洛谷題目頁面傳送門
題意見洛谷。
首先考慮實時維護\(\forall i,j\in[1,n]\),目前有多少時刻滿足能從\(i\)到達\(j\)。
顯然,\(i\)能到達\(j\)當且僅當\(i,j\)在同一個亮燈連續段里。將當前狀態剖成若干個極大亮燈連續段,那么能否到達關於\(i,j\)兩維的函數應該是由這些連續段,每段分別在\(i\)軸和\(j\)軸上作為兩條鄰邊、\(j=i\)上的一段作為副對角線的一些充滿\(1\)的正方形組成的,其他地方都是\(0\)。(別問為啥不貼圖,問就是我不是良心博主)
那么在某一時刻的答案函數就是之前所有時刻的所對應的上述01函數之和。考慮實時維護這個答案函數(二維函數,即一個矩陣)。
一種很容易想到的方法是遞推,每次將當前時刻的01函數加到當前維護的答案函數里面去。當前時刻的01函數顯然是由上一時刻的01函數進行常數次矩形修改得來的(開燈就是連接兩邊接壤的\(1\)矩形並補全,關燈就是從所在\(1\)矩形斷開,至於如何維護這些連續段,set
即可,太簡單不多說),而將01函數加到答案函數里去又等價於對答案矩陣進行若干次(這里不是常數次了,而是連續段個數次,即\(1\)矩形個數次)矩形增加\(1\)。這里對01函數的更新顯然是力所能及的,而對答案函數的更新的復雜度就沒法保證了。
不難發現,這里對01函數的更新是若干次矩形加\(1\),其中\(1\)是個常數,而這些矩形隨時刻遞增又是常數差異的,就一臉可以優化成在這些常數級別的差異上增加非常數,從而保證復雜度。
考慮一個01函數關於時刻的三維函數。考慮當前答案函數的每一處在這個三維函數上的意義:顯然是從當前時刻斷開時間軸得到的縱切面的左邊的所有此處的和。這是一些\(0/1\)的和,\(0\)顯然不用考慮,剩下來就是一些\(1\)的區間,設為\([l_1,r_1],[l_2,r_2],\cdots,[l_k,r_k]\),那么這個和就是\(\sum\limits_{i=1}^k(r_i-l_i+1)\)。考慮在一個區間開始的時候給此處貢獻上\(-l_i\),在區間結束的時候給此處貢獻上\(r_i+1\)。此時增加量顯然不是常數了,我們來看看增加次數有沒有減少。區間開始和區間結束的時候,就是相鄰01函數差異對此處影響的時候,每次時刻的遞推都只有01函數差異的矩形量次矩形增加,哦吼,可以了。需要注意的是,若當前要查詢的那處在01函數中為\(1\)的話,那么第\(k\)個區間還未結束,我們要強行令它結束,即在答案函數在此處的值的基礎上再加上當前時刻。
(上面這一段重要的轉化是本題的瓶頸。個人感覺這個哲學思想理解的還不是很透徹,大概以后重點做DS的時候題做多了感覺就上來了吧。)
接下來就是個矩形增加、單點查詢的事了。看起來能夠線段樹套動態開點線段樹,但寫到一半才發現外層線段樹的懶標記無法\(\mathrm O(1)\)存儲。於是想到將修改和查詢范圍顛倒的方式:差分。考慮二維差分,這樣一次矩形增加轉化為\(4\)次單點增加,單點查詢轉化為前綴矩形求和。這就是個經典的二維數點模型,寫一發BIT套動態開點線段樹即可(萌新第一次寫這個,多多關照)。二維數點應該是有其他方法的(如cdq分治),然而我還沒有系統的學這一塊,不管,而且這題重點不在這里。
btw,這是我第二次寫vector
動態開點數據結構(第一次是列隊),犯了跟列隊同樣的錯誤(UB+vector
分配內存原理造成賦不進去值)。多錯幾次就不會犯了。
代碼:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f;
int lowbit(int x){return x&-x;}
const int N=300000;
int n,qu;
char a[N+5];
struct segtree{//動態開點線段樹
struct node{int lson,rson,l,r,sum;};
#define lson(p) nd[p].lson
#define rson(p) nd[p].rson
#define l(p) nd[p].l
#define r(p) nd[p].r
#define sum(p) nd[p].sum
vector<node> nd;
int nwnd(int l=1,int r=n){return nd.pb(node({0,0,l,r,0})),nd.size()-1;}
void init(){//初始化
nd.pb(node({0,0,0,0,0}));
nwnd();
}
void add(int x,int v,int p=1){//單點增加
sum(p)+=v;
if(l(p)==r(p))return;
int mid=l(p)+r(p)>>1,res;
if(x<=mid){
if(!lson(p))res=nwnd(l(p),mid),lson(p)=res;
add(x,v,lson(p));
}
else{
if(!rson(p))res=nwnd(mid+1,r(p)),rson(p)=res;
add(x,v,rson(p));
}
}
int _sum(int l,int r,int p=1){//區間求和
if(!p)return 0;
if(l<=l(p)&&r>=r(p))return sum(p);
int mid=l(p)+r(p)>>1,res=0;
if(l<=mid)res+=_sum(l,r,lson(p));
if(r>mid)res+=_sum(l,r,rson(p));
return res;
}
};
struct bitree{//BIT
segtree segt[N+1];
void init(){//初始化
for(int i=1;i<=n;i++)segt[i].init();
}
void add(int x,int y,int v){
if(y>n)return;
while(x<=n)segt[x].add(y,v),x+=lowbit(x);
}
void add(int l1,int r1,int l2,int r2,int v){//矩形增加
add(r1+1,r2+1,v);add(l1,r2+1,-v);add(r1+1,l2,-v);add(l1,l2,v);//轉化為差分數組上的單點增加
}
int val(int x,int y){//單點查詢
int res=0;
while(x)res+=segt[x]._sum(1,y),x-=lowbit(x);//轉化為差分數組上的矩形求和
return res;
}
}bit;
int main(){
cin>>n>>qu;
scanf("%s",a+1);
set<pair<int,int> > st;
for(int i=1,las=0;i<=n+1;i++)
if(a[i]=='1')las=las?las:i;
else if(las)st.insert(mp(las,i-1)),las=0;
bit.init();
for(int i=1;i<=qu;i++){
char tp[10];int x,y;
scanf(" %s%d",tp,&x);
if(tp[0]=='t'){
set<pair<int,int> >::iterator fd=st.upper_bound(mp(x,inf));
if(fd!=st.begin()&&x<=(--fd)->Y){//關燈
int l=fd->X,r=fd->Y;
st.erase(fd);
bit.add(l,r,l,r,i);
if(l<x)st.insert(mp(l,x-1)),bit.add(l,x-1,l,x-1,-i);
if(r>x)st.insert(mp(x+1,r)),bit.add(x+1,r,x+1,r,-i);
}
else{//開燈
fd=st.upper_bound(mp(x,inf));
int l=x,r=x;
if(fd!=st.begin())fd--,fd->Y==x-1?bit.add(fd->X,fd->Y,fd->X,fd->Y,i),l=fd->X,st.erase(fd++):fd++;
if(fd!=st.end())fd->X==x+1&&(bit.add(fd->X,fd->Y,fd->X,fd->Y,i),r=fd->Y,st.erase(fd),0);
st.insert(mp(l,r));
bit.add(l,r,l,r,-i);
}
}
else{
scanf("%d",&y);y--;
set<pair<int,int> >::iterator fd=st.upper_bound(mp(x,inf));
if(fd!=st.begin()&&y<=(--fd)->Y)printf("%d\n",bit.val(x,y)+i);
else printf("%d\n",bit.val(x,y));
}
}
return 0;
}