關於樹論【動態點分治】
阿新 • • 發佈:2017-09-21
stop 其他 for pear 問題 oid 感謝 lac scanf
對於全局,開一個堆來維護每一個節點第二個堆的最大值加次大值,那麽全局的堆頂就是答案。
至於求樹上兩點距離,應用了dfs序+st表的做法。dep[i]表示的是1到i節點的距離。x到y之間的距離=dep[x]+dep[y]-2*dep[最近公共祖先],那麽如何來求解dep[最近公共祖先]呢 ,我用的是dfs序上ST表優化區間最小值,當然用倍增LCA也是可以的。
顯然x和y的最近公共祖先是x到y路徑上深度最小的點,如果我們用什麽方法把路徑上的點表示為連續的編號,那麽用ST表求區間最小值不就解決了嗎。
補充說明:
每個堆分成兩個堆。但改變顏色的操作,某些狀態就不合法了。那麽就需要刪除,但是堆每次只可以刪除堆頂,所以我們把每個堆分成兩個堆,一個堆記錄全部狀態,一個堆記錄沒用的狀態,這樣我們每次詢問堆頂的時候,如果堆頂是沒用的狀態,那麽我們就要刪除。
對於不會st表的同學,請看這裏。
st表可以解決rmq(區間最值)問題,LCA的樹上倍增算法也是類似於該思想,算法預處理時間復雜度O(nlogn),查詢時間O(1),但不支持在線修改。
利用DP的思想,得到一個數組mn[i][j],該數組表示從j到j+2^i-1的最小值,所以x到y的最小值表示為min(mn[t][x], mn[t][y-2^t+1])。
關於繼承的問題,mn[0][k]自然等於k自身的值,而mn[1][k]的值為min(mn [0][k], mn[0][k+1]),不難發現,mn[i][j]=min(mn[i-1][j], mn[i-1][j+2^(i-1)]。
代碼實現:
搬運:題意傳送門:http://caioj.cn/problem.php?id=1433
前幾天跟波* * * *老師一起搞這題,結果最後莫名其妙的被波老師D飛。。。
我用到的是動態點分治。
動態點分治就是基於樹的重心(一棵樹中,以重心為根的最大子樹的節點最小)上的解法,該解法將一棵樹分成若幹棵子樹,對每一棵子樹進行重新分配,一直往下分直到每一顆樹的節點數為1,上一層的重心需要連接下一層的重心,對這棵新樹來進行分治遞歸求解。
這道題的做法:
對於新樹每個節點我們用兩個堆來維護,每個節點的第一個堆維護以自己為根的子樹內所有節點到自己父親節點的距離。 第二個堆維護每個子節點第一個堆的堆頂(就是每一個孩子節點的子樹內裏自己最遠的距離) 那麽相對於每一個節點,第二個堆的最大值加次大值就是子樹內經過自己的最長鏈。
對於全局,開一個堆來維護每一個節點第二個堆的最大值加次大值,那麽全局的堆頂就是答案。
至於求樹上兩點距離,應用了dfs序+st表的做法。dep[i]表示的是1到i節點的距離。x到y之間的距離=dep[x]+dep[y]-2*dep[最近公共祖先],那麽如何來求解dep[最近公共祖先]呢 ,我用的是dfs序上ST表優化區間最小值,當然用倍增LCA也是可以的。
顯然x和y的最近公共祖先是x到y路徑上深度最小的點,如果我們用什麽方法把路徑上的點表示為連續的編號,那麽用ST表求區間最小值不就解決了嗎。
補充說明:
每個堆分成兩個堆。但改變顏色的操作,某些狀態就不合法了。那麽就需要刪除,但是堆每次只可以刪除堆頂,所以我們把每個堆分成兩個堆,一個堆記錄全部狀態,一個堆記錄沒用的狀態,這樣我們每次詢問堆頂的時候,如果堆頂是沒用的狀態,那麽我們就要刪除。
對於不會st表的同學,請看這裏。
st表可以解決rmq(區間最值)問題,LCA的樹上倍增算法也是類似於該思想,算法預處理時間復雜度O(nlogn),查詢時間O(1),但不支持在線修改。
利用DP的思想,得到一個數組mn[i][j],該數組表示從j到j+2^i-1的最小值,所以x到y的最小值表示為min(mn[t][x], mn[t][y-2^t+1])。
關於繼承的問題,mn[0][k]自然等於k自身的值,而mn[1][k]的值為min(mn [0][k], mn[0][k+1]),不難發現,mn[i][j]=min(mn[i-1][j], mn[i-1][j+2^(i-1)]。
代碼實現:
//Bin[i]表示2的i次方,Log[i]表示log(i) Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2; Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1; for(int i=1;i<=n;i++)mn[0][i]=a[i];、 for(int i=1;i<=Log[n];i++) for(int j=1;j<=n;j++) if(j+Bin[i]-1<=n)mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]);
感謝hanks_o(在度娘搜就行,ljhaoziyu剛Qtree7題的小夥伴)的傾情幫助和分析。
#include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; int Bin[20],Log[210000];//Bin[i]表示2的i次方,Log[i]就是log(i) struct node { int x,y,d,next; }a[210000];int len,last[110000]; void ins(int x,int y,int d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } struct heap { priority_queue<int> A,B;//A堆記錄存在的狀態,但因為堆不支持修改,另開一個B堆記錄淘汰的狀態 void push (int x){A.push(x);} void erase(int x){B.push(x);} void pop() { while(B.size()&&A.top()==B.top()){A.pop();B.pop();} A.pop(); } int size(){return A.size()-B.size();} int top() { while(B.size()&&A.top()==B.top()){A.pop();B.pop();} if(A.size()==0)return 0; return A.top(); } int stop() { if(size()<2)return 0; int x=top();pop(); int y=top();push(x); return y; } }A,B[110000],C[110000]; /* 每個節點的C堆維護所有子樹內的節點到自己父親節點的距離 B堆維護所有子節點第一個堆的堆頂(最大值) 那麽相對於每一個節點來說,第二個堆的最大值加次大值就是子樹內經過這個節點的最長鏈 全局維護A堆,記錄所有節點第二個堆的最大值和次大值的和。堆頂(最大值)就是答案 */ //mn[i][j]表示j到j+2^i-1點的最小深度,ys是新編號,dep[i]第i號節點表示到1號節點的距離 int z,mn[20][210000],ys[110000],dep[110000]; void dfs(int x,int f)//得dfs序,為st表準備 { mn[0][++z]=dep[x];ys[x]=z; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f) { dep[y]=dep[x]+a[k].d; dfs(y,x); mn[0][++z]=dep[x]; } } } /* getrt 找到樹的重心並存在G裏。樹的重心是一個點。 相比於其他點,以他為根的最大子樹最小 */ bool v[110000];//v表示這個點是否被訪問過 //G是divi的那棵樹的中心,sum是那棵樹的總節點數 //tot[i]是以i為根的樹節點數,g[i]是i為根的最大子樹的節點數 int G,sum,tot[110000],g[110000]; void getrt(int x,int f) { tot[x]=1;g[x]=0; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f&&v[y]==false) { getrt(y,x); tot[x]+=tot[y]; g[x]=max(g[x],tot[y]); } } g[x]=max(g[x],sum-tot[x]);//還有到父親這一條邊的子樹 if(g[x]<g[G])G=x; } //按照新的樹去建父子關系,當前這一層的中心去連接下一層的重心 int fa[110000]; void divi(int x) { v[x]=true; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(v[y]==false) { sum=tot[y];G=0; getrt(y,x); fa[G]=x;divi(G); } } } //求x到y路徑上深度最小的點的深度(最近公共祖先的深度 ) int rmq(int x,int y) { x=ys[x];y=ys[y]; if(x>y)swap(x,y); int t=Log[y-x+1]; return min(mn[t][x],mn[t][y-Bin[t]+1]); } //求x到y之間的距離 int dis(int x,int y) { return dep[x]+dep[y]-2*rmq(x,y); } void turn_black(int f,int x) { if(f==x) { if(B[f].size()==1) A.push(B[f].top()); } if(fa[f]==0)return ; int ff=fa[f],D=dis(ff,x),tmp=C[f].top(); C[f].push(D); if(D>tmp) { int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size(); if(tmp!=0)B[ff].erase(tmp); B[ff].push(D); int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size(); if(k1<k2) { if(s1>=2)A.erase(k1); if(s2>=2)A.push(k2); } } turn_black(ff,x); } void turn_white(int f,int x) { if(f==x) { if(B[f].size()==1) A.erase(B[f].top()); } if(fa[f]==0)return ; int ff=fa[f],D=dis(ff,x),tmp=C[f].top(); C[f].erase(D); if(D==tmp) { int k1=B[ff].top()+B[ff].stop(),s1=B[ff].size(); B[ff].erase(D); if(C[f].top()!=0)B[ff].push(C[f].top()); int k2=B[ff].top()+B[ff].stop(),s2=B[ff].size(); if(k1>k2) { if(s1>=2)A.erase(k1); if(s2>=2)A.push(k2); } } turn_white(ff,x); } int cc;bool col[110000]; char ss[5]; int main() { Bin[0]=1;for(int i=1;i<20;i++)Bin[i]=Bin[i-1]*2; Log[0]=-1;for(int i=1;i<=200000;i++)Log[i]=Log[i/2]+1; int n,m,x,y,c; scanf("%d",&n); len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&c); ins(x,y,c);ins(y,x,c); } dfs(1,0); //ST表 for(int i=1;i<=Log[z];i++) for(int j=1;j<=z;j++) if(j+Bin[i]-1<=z) mn[i][j]=min(mn[i-1][j],mn[i-1][j+Bin[i-1]]); //j到j+2^i-1的最小值等於min(前半部分最小值,後半部分最小值) G=0;g[0]=2147483647; sum=n;getrt(1,0); fa[G]=0;divi(G); for(int i=1;i<=n;i++) { turn_black(i,i); col[i]=true;cc++; } scanf("%d",&m); while(m--) { scanf("%s",ss+1); if(ss[1]==‘A‘) { if(cc<=0)printf("They have disappeared.\n"); else printf("%d\n",A.top()); } else { scanf("%d",&x); if(col[x]==true){turn_white(x,x);cc--;} else {turn_black(x,x);cc++;} col[x]=1-col[x]; } } return 0; }
關於樹論【動態點分治】