給定一棵n個點的樹,每條邊都有個長度,然後有Q次操作/詢問,每次可以佔領一個點,或是詢問某個點到所有已被佔領的點的距離總和。n,Q\leq 10^5。
作法:
這題有兩種作法,第一種作法是用平方分割,因為如果是一次佔領完再全部詢問,那麼我們可以用 DFS O(n) 求出每個點的答案,再O(1)回答詢問,詳細作法就不提了。而另外一種作法則是:把所有被佔領的點紀錄下來,每次詢問就直接去詢問給定點和所有被佔領點的距離總和,這樣一次的複雜度會是O(被佔領點個數\cdot logn)。於是我們考慮把兩種作法合併,當一個一個佔領點時,如果此時佔領的點還沒有那麼多,則直接用求距離再加起來的作法硬算,而當要硬算的點個數達到某個值(假設為K)時,就 DFS 一次把所有點的答案都更新好,這樣在詢問時我們就有了詢問的點到前K個被佔領點的距離總和了,剩下的部份就硬算即可。這樣可以得到單次詢問的複雜度為O(Klogn),佔領一個點時因為我們不會 DFS 超過\frac{Q}{K}次,因此複雜度會是O(\frac{Q}{K}n)。由這兩式就可以知道取K讓QKlogn=\frac{Q}{K}n會是最好的,移項得K=\sqrt{\frac{n}{logn}},總複雜度為O(Q\sqrt{nlogn})。但考試時不知道為什麼怎麼取K都TLE,後來乾脆取個100還200就莫名其妙的過了。
事實上可以做到單次詢問O(logn),用的東西叫重心剖分,他的概念是先對原本的樹取重心,把樹分成很多個子樹,再對這些子樹遞迴下去取重心,並且將原樹的重心和其子樹們的重心連起來,這樣我們就得到了一棵重心樹(重心樹上的邊不一定在原樹上出現,但每個點都出現了),不難證明其深度會是O(logn)的(之後稱重心樹上的一個子樹為重心子樹)。因此當詢問一個點x的答案時,可以直接沿著重心樹往上走(因此要紀錄好每個點在重心子樹上的父親是誰),並在走的過程維護當前答案等於「x到當前重心子樹裡所有已被佔領點的距離總和」,這樣一路走到根就獲得答案了。接下來我們要知道如何維護好答案,記當前重心子樹為T,他的根P,還有如果只看原樹中這棵重心子樹裡的所有點的話,把P拔掉會讓他分成許多(原樹中的)子樹,稱其為T_1,...,T_r,並且不妨設x落在T_1裡面。那麼此時所求答案就必須要增加「x到所有T_2,...,T_r中被佔領點的距離」。這可以拆成兩部份,因為x走到這些點都必須經過P,因此如果假設在T_2,...,T_r中被佔領的點共有t個,那麼就可以把所求改成t\cdot dist(P,x)+P到T_2,...,T_r中被佔領點的距離總和(其中dist 代表兩點在原樹中的距離)。對於前者,我們可以在預處理時,對於每個重心子樹,把他的根到所有他在重心子樹中的點的(在原樹中的)距離都存起來,這樣就能知道dist(P,x)了。再來則是要知道t,而這可以用「T中的被佔領點數扣掉T_1中的被佔領點數」得到,因此只要在多佔領一個點時沿重心樹走上去,維護好每個重心子樹中的被佔領點數就好了。最後則是要知道P到T_2,...,T_r中所有被佔領點的距離總和,這可以拆成「P到T中所有被佔領點的距離總和」扣掉「P到T_1中所有被佔領點的距離總和」,因此也只要在新佔領點時維護好這兩個值就可以了,都是利用到已算好的「重心到其子樹中某個點的距離」。
實作有很多寫法,但大部份都很麻煩,畢竟我們要存P的每個子樹的一些資訊,還有P到其子樹內每個點的距離。一個比較簡潔的記法是,首先我們先對每個點紀錄他是落在重心樹深度多少的地方,那麼如果我們想要紀錄某個重心P到某個點x的距離,就把這個值存在dis[dep[P]][x]裡面就可以了,其中dep代表這個點在重心樹上的深度,不難發現這個存法不會讓兩個要存的東西碰撞。再來也蠻麻煩的則是要存:對於每個重心P,紀錄他的每個子樹中被佔領的點離他的距離總和,而這也可以只用一個陣列來存,假設P在重心樹上落在T_1的子節點為Q,那麼就直接把P到T_1中被佔領點的距離總和存到Q的位子就可以了,也不難發現這樣不會碰撞。另外剩下的「P這個重心子樹中所有被佔領點到他的距離總和和有幾個被佔領點」當然只要存在P的位子就可以了。
code :
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn=100000+10,LOG=20 ; struct P{int to,dis;}; vector<P> v[maxn] ; int size[maxn] ; LL dis[LOG][maxn] ; int fa[maxn],vis[maxn] ; int gfa[maxn],dep[maxn] ; LL sum[maxn],sum2[maxn] ; int num[maxn] ; int get_cent(int st) { static int qu[maxn] ; int sz=0 ; qu[sz++]=st ; fa[st]=-1 ; for(int i=0;i<sz;i++) for(auto j : v[qu[i]]) if(j.to!=fa[qu[i]] && dep[j.to]==-1) fa[j.to]=qu[i] , qu[sz++]=j.to ; int ret=-1 , M=maxn ; for(int i=sz-1;i>=0;i--) { int x=qu[i],ma=0 ; size[x]=1 ; for(auto j : v[x]) if(j.to!=fa[x] && dep[j.to]==-1) size[x]+=size[j.to] , ma=max(ma,size[j.to]) ; ma=max(ma,sz-size[x]) ; if(ma<M) M=ma , ret=x ; } return ret ; } void dfs(int x,int f,LL di,int Gdep) { dis[Gdep][x]=di ; for(auto i : v[x]) if(i.to!=f && dep[i.to]==-1) dfs(i.to,x,di+i.dis,Gdep) ; } void cent_decomp(int x,int Gfa,int Gdep) { x=get_cent(x) ; gfa[x]=Gfa ; dep[x]=Gdep ; dfs(x,-1,0,Gdep) ; for(auto i : v[x]) if(dep[i.to]==-1) cent_decomp(i.to,x,Gdep+1) ; } void modify(int x,int cent,int son,int Gdep) { if(cent==-1) return ; num[cent]++ ; if(son==-1){modify(x,gfa[cent],cent,Gdep-1) ; return ;} sum[cent]+=dis[Gdep][x] ; sum2[son]+=dis[Gdep][x] ; modify(x,gfa[cent],cent,Gdep-1) ; } LL query(int x,int cent,int son,int Gdep) { if(cent==-1) return 0 ; LL ret= son==-1 ? sum[cent] : (num[cent]-num[son])*dis[Gdep][x]+sum[cent]-sum2[son] ; return ret+query(x,gfa[cent],cent,Gdep-1) ; } bool occ[maxn] ; main() { int n,Q ; scanf("%d%d",&n,&Q) ; for(int i=1;i<n;i++) { int x,y,di ; scanf("%d%d%d",&x,&y,&di) ; v[x].push_back((P){y,di}) ; v[y].push_back((P){x,di}) ; } memset(dep,-1,sizeof(dep)) ; cent_decomp(0,-1,0) ; while(Q--) { int t,x ; scanf("%d%d",&t,&x) ; if(t==1 && !occ[x]) occ[x]=1 , modify(x,x,-1,dep[x]) ; else if(t==2) printf("%lld\n",query(x,x,-1,dep[x])) ; } }
沒有留言:
張貼留言