1. 程式人生 > 實用技巧 >【洛谷5445】[APIO2019] 路燈(樹套樹)

【洛谷5445】[APIO2019] 路燈(樹套樹)

點此看題面

大致題意:\(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;
}