1. 程式人生 > >BZOJ.3510.首都(LCT 啟發式合並 樹的重心)

BZOJ.3510.首都(LCT 啟發式合並 樹的重心)

root con res 判斷 spa down else 超過 html

題目鏈接 BZOJ
洛谷

詳見這.

求所有點到某個點距離和最短,即求樹的重心。考慮如何動態維護。
兩棵子樹合並後的重心一定在兩棵樹的重心之間那條鏈上,所以在合並的時候用啟發式合並,每合並一個點檢查sz[]大的那棵子樹的重心(記為root)最大子樹的sz[] * 2是否>n;
若>n,則向fa移動一次(先把合並點Splay到根)。重心還一定是在sz[]大的那棵子樹中,且移動次數不會超過sz[]小的子樹的點數(所以總移動次數不會超過O(n)?)。
復雜度 \(O(nlog^2n)\)
具體實現。。想通了真是特別簡單。。也就想想簡單
先Link,然後從心向右中序遍歷(深度大是向右!).
每到一個點判斷其右子樹和虛樹的sz和 * 2是否>當前根的sz,如果是,則當前點是要找的重心,繼續下一個。若sz[x] * 2 < sz[root],那麽root就是根了。

可能有多個重心,這些重心應該是連續的一段。因為編號最小,若sz[x] * 2==sz[root] && id[x]<id[root],則更新root繼續。
用並查集維護重心。(Find_root()太慢了)
(不需要再算左子樹取max,及深度小於它的子樹大小,因為既然重心在那邊就不會大過另一邊,更何況另一邊還又插入了一棵子樹)
(維護子樹最大值找重心不行吧,合並更新好像很麻煩,直接用sz[]就可以找).

為什麽一定要Make_root()重心。。突然覺得根本不明白LCT,不知道怎麽走的了。唉以後再說吧。。先水過去
註意用點的sz[]前先Splay()更新!
至於更新重心可以通過縮短左右區間 能到O(nlogn)的方法(還比這個好理解?)以後再做吧

博客園Markdown寫乘號必須要帶個空格防止成斜體嗎。。

//4044kb    2256ms
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
const int N=1e5+5;

#define lson son[x][0]
#define rson son[x][1]

int n,m,Ans,sz[N],sz_i[N],_fa[N],fa[N],son[N][2],sk[N];
bool tag[N];
inline void Update(int
x){ sz[x]=sz[lson]+sz[rson]+sz_i[x]+1; } inline bool n_root(int x){ return son[fa[x]][0]==x||son[fa[x]][1]==x; } inline void Rev(int x){ std::swap(lson,rson), tag[x]^=1; } inline void PushDown(int x){ if(tag[x]) Rev(lson),Rev(rson),tag[x]=0; } void Rotate(int x) { int a=fa[x],b=fa[a],l=son[a][1]==x,r=l^1; if(n_root(a)) son[b][son[b][1]==a]=x; if(son[x][r]) fa[son[x][r]]=a; fa[a]=x, fa[x]=b, son[a][l]=son[x][r], son[x][r]=a; Update(a); } void Splay(int x) { int t=1,a=x; sk[1]=x; while(n_root(a)) sk[++t]=a=fa[a]; while(t) PushDown(sk[t--]); while(n_root(x)) { a=fa[x]; if(n_root(a)) Rotate(son[a][1]==x^son[fa[a]][1]==a?x:a); Rotate(x); } Update(x); } void Access(int x){ for(int pre=0; x; x=fa[pre=x]) Splay(x), sz_i[x]+=sz[rson]-sz[pre], rson=pre;//Update(x); } void Make_root(int x){ Access(x), Splay(x), Rev(x); } void Split(int x,int y){ Make_root(x), Access(y), Splay(y); } int Find_root(int x) { Access(x), Splay(x); while(lson) x=lson; return x; } void Link(int x,int y){ Split(x,y), sz_i[y]+=sz[x], fa[x]=y, Update(y); } int Get_fa(int x){ return x==_fa[x]?x:_fa[x]=Get_fa(_fa[x]); } //int Next(int x){//? // PushDown(x), x=rson; // while(lson) PushDown(x),x=lson; // return x; //} int q[N],cnt; void DFS(int x,int lim) { PushDown(x); if(lson) DFS(lson,lim); if(cnt>lim) return; q[++cnt]=x; if(cnt>lim) return; if(rson) DFS(rson,lim); } int Union(int x,int y) { int r1=Get_fa(x),r2=Get_fa(y); // int r1=Find_root(x),r2=Find_root(y); Splay(r1), Splay(r2);//先Splay更新sz[]! if(sz[r1]>sz[r2]||(sz[r1]==sz[r2]&&y>x)) std::swap(x,y),std::swap(r1,r2);//x->y r1->r2 int lim=sz[r1],res=r2,tot=sz[r2]+sz[r1]; Link(x,y), Access(x), Splay(r2); cnt=0; DFS(r2,lim); for(int sum,i=1; i<=cnt; ++i) { Splay(q[i]), sum=sz[son[q[i]][1]]+sz_i[q[i]]+1; // if(tot<sum<<1) res=q[i];//不這麽寫是不是有點問題啊 // else if(tot==sum<<1) res=std::min(res,q[i]); if(tot<sum<<1||(tot==sum<<1&&q[i]<=res)) res=q[i]; else break; } Make_root(res); _fa[r1]=_fa[r2]=_fa[res]=res/*更新重心*/; return res; } inline int read() { int now=0;register char c=gc(); for(;!isdigit(c);c=gc()); for(;isdigit(c);now=now*10+c-'0',c=gc()); return now; } int main() { n=read(),m=read(); int res=0,x,y; char opt[5]; for(int i=1; i<=n; ++i) _fa[i]=i, res^=i, sz[i]=1; while(m--) { scanf("%s",opt); if(opt[0]=='A') x=read(),y=read(),res^=Get_fa(x)^Get_fa(y)^Union(x,y); else if(opt[0]=='Q') x=read(),printf("%d\n",Get_fa(x));//printf("%d\n",Find_root(x)); else printf("%d\n",res); } return 0; }

BZOJ.3510.首都(LCT 啟發式合並 樹的重心)