感覺又開了一個天坑真是開心...
還是先貼神犇鏈接補充閱讀吧:
http://lazycal.logdown.com/posts/202331-bzoj3572
http://www.cnblogs.com/wuyuhan/p/5521249.html
例題 bzoj3572 世界樹
給定一棵樹,有若干個詢問,每次給定m個點,每個點都被這m個點中最近(距離相同,編號小的近)的點管轄。問m個點分別管幾個點。
n<=300000,q<=300000,∑m<=300000。
首先我們發現∑m是不大的,所以我們可以對於每一個詢問點亂搞。
我們發現這類題目中我們可以把一些詢問點單獨搞出來組成一棵樹,這樣只有這些詢問點是有用的。哦不對,還有一些詢問點的lca?
為了和諧,我們來直觀地感受一下...比如這里我生成了一棵20個點的樹。藍色的是詢問點。紅色點就會在虛樹上。
觀察這些虛樹上的點,我們發現我們似乎是需要把詢問點按dfs序排序一下,然后把相鄰點取個lca然后亂搞一下?
聽起來好像很有道理,不過這™能寫?
基於這樣的想法,我們來考慮另外一種做法,我們用一個棧來維護虛樹的“當前這一坨東西”...例如我們在棧中加入了18。然后接下來打算加入一個16。
我們現在發現這個棧頂的lca,也就是2,是有用的,那么我們現在就要把18彈掉,換成2,然后再扔進去一個16。
接下來我們要加一個20,那么它與棧頂的lca為2。我們就考慮16,16是沒用的,把它彈掉,然后看見2,正好就是lca,就保留。
類似這樣我們可以發現開始把所有詢問點加入虛樹后,我們把詢問點按dfs序排個序,這時棧里面應該維護一個奇怪的玩意,先計算一個新加的點與棧頂的lca,然后如果一個棧里的東西一直都“沒用”,也就是深度比這個lca來得大,就一直彈出棧頂,最后如果棧頂不是lca,就把lca加入棧,並且加入虛樹,然后再加入這個點。
這樣我們就可以求出虛樹啦,同時我們也可以得到虛樹上每一個點的父親節點。
(友情提示:下文的某些地方可能會用控制點來代替最近點,反正就是一個意思)
接下來我們考慮如何求出虛樹上每個點被哪個點控制。我們可以用一個pair<int,int>來存它到控制點的距離和控制點,然后我們用每一個點更新它的父親,再用每一個父親更新它的孩子。注意到因為所有虛樹上的點的lca也在虛樹上,所以對於虛樹上某一個點和某一個控制點,第一遍必然會更新到lca,第二遍必然會更新到這個點,所以這個做法是正確的。
然后我們要考慮虛樹以外的點要怎么維護。
比如對於這樣一個小樹,紅色的點在虛樹上。
我們考慮虛樹上的一條父子邊,對應實際的樹上就會是一坨東西。比如這棵樹上2-5就對應4和6。
具體地說,對於一條父子邊(f,x),f=fa[x],那么對應實際樹上的就是f在x方向的這棵子樹除掉x這棵子樹。
例如2-5就表示4這棵子樹除掉5這棵子樹,就是4和6這兩個點。
我們發現樹上除了這些父子邊,還有一些沒有被計入統計的點,需要分別考慮,虛樹根往上的都需要單獨統計,例如1,還有像圖中的3這樣也不會被統計到。
注意到跟這些點最近的點必然和和這些點相連的點最近的是同一個點,比如1和3最近的都與2最近的一樣。
現在,如果5與2最近的是同一個點,那么4和6必然也是這一個點。
否則我們可以發現,這是與深度有關的。比如2最近的點->2距離為p,5最近的點->5距離為q。
那么與控制5的點距離不超過(p+q+2到5距離)/2的點都應該選擇5最近的點,那么深度就要>=dep[5]-(near_dis(2)+near_dis(5)+dis(2,5))/2+near_dis(5)。
只要從5開始往上跳到這個深度,計算一下就可以了。
需要注意的是,如果(p+q+2到5距離)是偶數的話,一定會有一個(些)點到2和5最近的點距離相同,那么這個(些)點應該選編號小的一個。即因為正常情況下這些點會選擇下面那個點的控制點,所以如果上面那個點控制點編號小,就要考慮深度++。
所以基本的思路都清楚了,我們只要寫一個倍增維護一下lca和往上跳這種東西就可以了。復雜度大概是O((n+q+∑m)logn)的。
代碼終於寫完啦~感人肺腑
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <set> #include <map> using namespace std; int inf=1000000000; #define gc getchar() int g_i() { int tmp=0; bool fu=0; char s; while(s=gc,s!='-'&&(s<'0'||s>'9')) ; if(s=='-') fu=1; else tmp=s-'0'; while(s=gc,s>='0'&&s<='9') tmp=tmp*10+s-'0'; if(fu) return -tmp; else return tmp; } #define gi g_i() #define pob #define pc(x) putchar(x) namespace ib {char b[100];} inline void pint(int x) { if(x==0) {pc(48); return;} if(x<0) {pc('-'); x=-x;} char *s=ib::b; while(x) *(++s)=x%10, x/=10; while(s!=ib::b) pc((*(s--))+48); } #define SZ 666666 #define D 20 #define _els ;else //real tree namespace rt { int n,fst[SZ],nxt[SZ],vb[SZ],fa[SZ],up[SZ][D],dep[SZ],M=0,dfsn[SZ],C=0,sz[SZ]; void ad_de(int a,int b) {++M; nxt[M]=fst[a]; fst[a]=M; vb[M]=b;} void adde(int a,int b) {ad_de(a,b); ad_de(b,a);} void dfs(int p) { dfsn[p]=++C; sz[p]=1; for(int e=fst[p];e;e=nxt[e]) { int b=vb[e]; if(b==fa[p]) continue; fa[b]=up[b][0]=p; dep[b]=dep[p]+1; dfs(b); sz[p]+=sz[b]; } } void build() { dfs(1); for(int g=1;g<D;g++) { for(int i=1;i<=n;i++) { if(up[i][g-1]) up[i][g]=up[up[i][g-1]][g-1]; } } } //jump up (x=fa[x]) until dep[x]=d int jmp(int x,int d) { for(int i=D-1;i>=0;i--) { if(!up[x][i]||dep[up[x][i]]<d)_els x=up[x][i]; } return x; } int lca(int x,int y) { if(dep[x]>dep[y]) swap(x,y); y=jmp(y,dep[x]); if(x==y) return x; for(int i=D-1;i>=0;i--) { if(up[x][i]!=up[y][i]) x=up[x][i], y=up[y][i]; } return fa[x]; } } //virtual tree namespace vt { #define f_ first #define s_ second typedef pair<int,int> pii; //vs: points in vtree int sn,ss[SZ],vn,vs[SZ],stn=0,st[SZ],vfa[SZ],emp[SZ],vfe[SZ],anss[SZ],ss_[SZ]; pii ks[SZ]; //(dis,controller) bool cmp_dfsn(int a,int b) {return rt::dfsn[a]<rt::dfsn[b];} void build() { vn=stn=0; for(int i=1;i<=sn;i++) ss_[i]=ss[i]; //backup sort(ss+1,ss+1+sn,cmp_dfsn); for(int i=1;i<=sn;i++) vs[++vn]=ss[i], ks[ss[i]]=pii(0,ss[i]), anss[ss[i]]=0; for(int i=1;i<=sn;i++) { int x=ss[i]; if(!stn) {st[++stn]=x; vfa[x]=0; continue;} int lca=rt::lca(x,st[stn]); for(;rt::dep[st[stn]]>rt::dep[lca];--stn) { if(rt::dep[st[stn-1]]<=rt::dep[lca]) vfa[st[stn]]=lca; } if(st[stn]!=lca) { vs[++vn]=lca; ks[lca]=pii(inf,0); vfa[lca]=st[stn]; st[++stn]=lca; } vfa[x]=lca; st[++stn]=x; } //注意到按dfs序排序是滿足兒子一定在父親的后面的 sort(vs+1,vs+1+vn,cmp_dfsn); for(int i=1;i<=vn;i++) { int x=vs[i]; emp[x]=rt::sz[x]; if(i>1) vfe[x]=rt::dep[x]-rt::dep[vfa[x]]; } for(int i=vn;i>1;i--) { int x=vs[i],f=vfa[x]; ks[f]=min(pii(ks[x].f_+vfe[x],ks[x].s_),ks[f]); } for(int i=2;i<=vn;i++) { int x=vs[i],f=vfa[x]; ks[x]=min(pii(ks[f].f_+vfe[x],ks[f].s_),ks[x]); } for(int i=1;i<=vn;i++) { int x=vs[i],f=vfa[x]; //樹根往上的點 if(i==1) {anss[ks[x].s_]+=rt::n-rt::sz[x]; continue;} int f_p=rt::jmp(x,rt::dep[f]+1),cnt=rt::sz[f_p]-rt::sz[x]; emp[f]-=rt::sz[f_p]; //這一棵子樹已經處理過了 if(ks[f].s_==ks[x].s_) {anss[ks[x].s_]+=cnt; continue;} int md=rt::dep[x]-(ks[f].f_+ks[x].f_+vfe[x])/2+ks[x].f_; if((ks[f].f_+ks[x].f_+vfe[x])%2==0&&ks[f].s_<ks[x].s_) ++md; int dsz=rt::sz[rt::jmp(x,md)]-rt::sz[x]; //down_sz anss[ks[x].s_]+=dsz; anss[ks[f].s_]+=cnt-dsz; } for(int i=1;i<=vn;i++) anss[ks[vs[i]].s_]+=emp[vs[i]]; for(int i=1;i<=sn;i++) { pint(anss[ss_[i]]); pc(' '); } pc(10); } } int main() { rt::n=gi; for(int i=1;i<rt::n;i++) { int x=gi,y=gi; rt::adde(x,y); } rt::build(); int q=gi; while(q--) { vt::sn=gi; for(int i=1;i<=vt::sn;i++) vt::ss[i]=gi; vt::build(); } }