「APIO2019」路燈 (K-D Tree / 樹套樹 / CDQ + 樹狀陣列)
阿新 • • 發佈:2020-07-26
「APIO2019」路燈 (K-D Tree / 樹套樹 / CDQ + 樹狀陣列)
首先想到一個簡單的問題轉化
對於一個詢問,聯通的時間是若干連續的區間\([L_i,R_i]\)
所有的\(L_i,R_i+1\)都是關鍵點,即由不連通變為聯通的時間 和 由聯通變為不連通的時間
把答案轉化為\(\sum R_i+1-L_i\)即可
問題轉化為對於當前的操作,找到它是那些詢問的關鍵點
如果是合併操作,被合併的兩個區間之間變得聯通
如果是分裂操作,裂開的兩個區間之間不再聯通
可以用set維護上述區間,發現每次被更新的值都是一個二維區間
算上時間這一維,問題轉化為一個類 三維偏序問題,但是題限制了記憶體
Part1 K-D Tree
限制了記憶體,很容易想到直接K-D Tree,實際執行也比較優秀
注意可以把要詢問的點拿出來建出K-D Tree,每次區間修改即可
時間複雜度\(O(n\sqrt n)\),空間複雜度\(O(n)\)
#include<bits/stdc++.h> using namespace std; #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i) #define pb push_back typedef pair <int,int> Pii; #define mp make_pair void cmin(int &a,int b){ ((a>b)&&(a=b)); } void cmax(int &a,int b){ ((a<b)&&(a=b)); } char IO; int rd(){ int s=0; while(!isdigit(IO=getchar())); do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())); return s; } const int N=3e5+10,INF=1e9; int n,m,rt,col[N],opt[N],a[N],b[N]; char str[N]; set <pair <int,int> > st,tmp; struct Node{ int x,y; } A[N]; int cmp1(Node a,Node b){ return mp(a.x,a.y)<mp(b.x,b.y); } int cmp2(Node a,Node b){ return mp(a.y,a.x)<mp(b.y,b.x); } int ch[N][2],lx[N],rx[N],ly[N],ry[N],s[N],t[N]; int Build(int l,int r,int d=0) { if(l>r) return 0; int u=(l+r)>>1; nth_element(A+l,A+u,A+r+1,d?cmp2:cmp1); ch[u][0]=Build(l,u-1,d^1),ch[u][1]=Build(u+1,r,d^1); lx[u]=rx[u]=A[u].x,ly[u]=ry[u]=A[u].y; for(int i:ch[u]) if(i) cmin(lx[u],lx[i]),cmin(ly[u],ly[i]),cmax(rx[u],rx[i]),cmax(ry[u],ry[i]); return u; } void Upd(int x1,int x2,int y1,int y2,int u,int x) { if(!u || x1>rx[u] || x2<lx[u] || y1>ry[u] || y2<ly[u]) return; if(x1<=lx[u] && rx[u]<=x2 && y1<=ly[u] && ry[u]<=y2) return void(s[u]+=x); if(x1<=A[u].x && A[u].x<=x2 && y1<=A[u].y && A[u].y<=y2) t[u]+=x; for(int i:ch[u]) Upd(x1,x2,y1,y2,i,x); } int Que(Node x,int u,int d=0) { if(A[u].x==x.x && A[u].y==x.y) return s[u]+t[u]; int y=ch[u][!(d?cmp2(x,A[u]):cmp1(x,A[u]))]; return Que(x,y,d^1)+s[u]; } int main(){ n=rd(),m=rd(); scanf("%s",str+1); rep(i,1,n) col[i]=str[i]-'0'; rep(i,1,n+1) { int r=i; while(col[r]) r++; st.insert(mp(i,r)); i=r; } rep(i,1,m) { scanf("%s",str+1); if(str[1]=='t') opt[i]=1,a[i]=rd(); else opt[i]=2,a[i]=rd(),b[i]=rd(),tmp.insert(mp(a[i],b[i])); } n=0; for(auto it:tmp) A[++n]=(Node){it.first,it.second}; rt=Build(1,n); rep(i,1,m) { if(opt[i]==1) { int x=a[i]; if(col[x]) { auto it=st.upper_bound(mp(x,INF)); it--; int l=it->first,r=it->second; st.erase(it); st.insert(mp(l,x)),st.insert(mp(x+1,r)); Upd(l,x,x+1,r,rt,i); } else { auto it=st.upper_bound(mp(x,INF)),tmp=it; it--; int l=it->first,r=tmp->second; st.erase(it),st.erase(tmp); st.insert(mp(l,r)),Upd(l,x,x+1,r,rt,-i); } col[x]^=1; } else { int ans=Que((Node){a[i],b[i]},rt); auto it=st.upper_bound(mp(a[i],INF)); it--; if(it->second>=b[i]) ans+=i; printf("%d\n",ans); } } }
Part2 樹套樹(沒有程式碼)
由於已知查詢的節點,樹套樹的記憶體可以優化到\(O(n\log n)\)
把要詢問的點拉出來,每次詢問在第二維中有\(\log n\)次單點查詢,所以需要被查詢的位置一共只有\(n\log n\)個
把這些會被查詢的位置拿出來建成\(n\)棵靜態的線段樹,更新就直接在這些靜態的線段樹上區間更新即可
時間複雜度\(O(n\log ^2 n)\),空間複雜度\(O(n\log n)\)
Part3 CDQ+樹狀陣列
是常規寫法,不會被限制記憶體
CDQ一維,排序一維,樹狀陣列一維,參見三維偏序