【洛谷5445】[APIO2019] 路燈(樹套樹)
阿新 • • 發佈:2020-07-25
大致題意: 有\(n\)個點,規定\(x,y\)連通當且僅當\(a_x=a_{x+1}=...=a_y=1\)。給定零時刻\(a_i\)的值,每個時刻可能會發生兩種事件:將\(a_x\)取反(\(0->1,1->0\)),或詢問\(x,y\)有多少個時刻連通。
樹套樹
這道題一眼樹套樹,然後隨便推了推就推出來了,應該算是一道比較水的題目吧。
考慮用平面上一點\((x,y)\)表示\(x,y\)的答案。
一個基本性質,如果\(x,y\)連通,則\(x,y\)之間的所有點都是連通的。
於是我們可以摳出序列中每一整段全是\(1\)的區間,然後類似於\(ODT\)用\(set\)進行維護,其中每個區間要維護它的誕生時間。
每次改變一個點的值的時候,無非就是連通若干區間或是斷開一個區間。
無論是連通還是斷開,都需要先計算原區間的貢獻(用當前時間減去區間誕生時間)並從\(set\)中刪去,然後往\(set\)中加入新的區間。
對於\([l,r]\)區間,它的貢獻範圍就是左上角為\((l,l)\)、右下角為\((r,r)\)的一個矩形。
因此只要區間修改、單點查詢,樹狀陣列套線段樹即可。
程式碼
#include<bits/stdc++.h> #define Tp template<typename Ty> #define Ts template<typename Ty,typename... Ar> #define Reg register #define RI Reg int #define Con const #define CI Con int& #define I inline #define W while #define N 300000 using namespace std; int n,a[N+5];char s[N+5]; struct V//儲存一個區間的資訊 { int l,r,t;I V(CI a=0,CI b=0,CI c=0):l(a),r(b),t(c){}//l,r為左右端點,t為誕生時間 I bool operator < (Con V& o) Con {return l<o.l;} };set<V> S;typedef set<V>::iterator IT;//用set維護 class SegmentArray//樹狀陣列套線段樹 { private: int Rt[N+5];class SegmentTree//動態開點線段樹 { private: int Nt;struct node {int V,S[2];}O[N*300]; public: I void U(int& rt,CI L,CI R,CI v,CI l=1,CI r=n)//區間修改 { if(!rt&&(rt=++Nt),L<=l&&r<=R) return (void)(O[rt].V+=v);RI mid=l+r>>1; L<=mid&&(U(O[rt].S[0],L,R,v,l,mid),0),R>mid&&(U(O[rt].S[1],L,R,v,mid+1,r),0); } I int Q(int& rt,CI p,CI l=1,CI r=n)//單點查詢 { if(!rt||l==r) return O[rt].V;RI mid=l+r>>1; return (p<=mid?Q(O[rt].S[0],p,l,mid):Q(O[rt].S[1],p,mid+1,r))+O[rt].V; } }S; I void U(RI x,CI l,CI r,CI v) {W(x<=n) S.U(Rt[x],l,r,v),x+=x&-x;} public: I void U(CI l,CI r,CI v) {U(l,l,r,v),U(r+1,l,r,-v);}//區間修改 I int Q(RI x,CI y,RI t=0) {W(x) t+=S.Q(Rt[x],y),x-=x&-x;return t;}//單點查詢 }T; #define Find(x) --S.upper_bound(x)//在set中找到對應區間 I void On(CI ti,CI x)//開啟 { IT v;RI l=x,r=x;a[x+1]&&(v=Find(x+1),T.U(v->l,v->r,ti-v->t),r=v->r,S.erase(v),0),//若右邊存在區間,計算貢獻並刪去 a[x-1]&&(v=Find(x-1),T.U(v->l,v->r,ti-v->t),l=v->l,S.erase(v),0),S.insert(V(l,r,ti));//若左邊存在區間,計算貢獻並刪去;最後加入新區間 } I void Off(CI ti,CI x)//關掉 { IT v=Find(x);RI l=v->l,r=v->r;T.U(l,r,ti-v->t),S.erase(v),//計算貢獻並刪去 l^x&&(S.insert(V(l,x-1,ti)),0),r^x&&(S.insert(V(x+1,r,ti)),0);//斷成兩個新區間 } I int Ask(CI ti,CI x,CI y)//求出尚未統計的答案 { if(!a[x]) return 0;IT v=Find(x);return v->r>=y?ti-v->t:0;//如果在同一連通塊中才有貢獻 } int main() { RI Qt,i,j;for(scanf("%d%d%s",&n,&Qt,s+1),i=1;i<=n;++i) a[i]=s[i]&1;//讀入資料 for(i=1;i<=n;i=j+1) if(a[j=i]) {W(j^n&&a[j+1]) ++j;S.insert(V(i,j,0));}//初始化摳區間 RI x,y;for(i=1;i<=Qt;++i) switch(scanf("%s%d",s+1,&x),s[1])//處理操作 { case 't':(a[x]^=1)?On(i,x):Off(i,x);break;//修改 case 'q':scanf("%d",&y),--y,printf("%d\n",T.Q(x,y)+Ask(i,x,y));break;//詢問 }return 0; }