[WC2018]即時戰略——動態點分治(替罪羊式點分樹)
題目連結:
題目大意:給一棵結構未知的樹,初始時除1號點其他點都是黑色,1號點是白色,每次你可以詢問一條起點為白色終點任意的路徑,互動庫會自動返回給你這條路徑上與起點相鄰的節點並且如果這個點為黑色則將它變為白色,要求在不多於給定次數的詢問內使所有點變為白色。
大致思路為按一定順序分別將n-1個點變為白點,為了防止被卡,需要對2~n的序列隨機打亂再按打亂後的順序逐個變白。
資料範圍分為三種,分開講解(假設當前要變白的點為x):
一、完全二叉樹
這一部分比較簡單,我們只需要按照一定順序將每個點都變為白點即可。對於將每個點x變為白色的過程,因為可以確定上次詢問的點一定是白點,所以我們每次詢問以上次詢問所返回的點為起點(第一次以根節點即1號點為起點),以x為終點詢問,直到返回點為x為止。因為樹高嚴格logn,詢問次數為nlogn。
二、鏈
可以發現被變白的點一定是連續的一段,我們記錄這一段的左右端點,每次先詢問從一個端點到x的路徑,如果返回點為白點說明x在1號點的另一邊,再一直詢問從另一個端點到x的路徑並更新端點,直到x被變白為止。這樣期望詢問次數為n+logn(最大為2n),為了防止被卡,建議每次首先詢問的端點左右交替,不要一直先詢問左端點或右端點。
三、無限制
這一部分是本題的重點,題目要求的詢問數上限為nlogn。可以發現白點一定組成一個聯通塊,對於每個待尋找的點,只要找到當前所有白點中與它相連的點即可。從完全二叉樹的做法中我們可以得到啟發:如果在尋找每個點的過程中只遍歷到logn個節點,那麼就能滿足要求。而原樹樹高上限是O(n),無法在原樹上直接尋找,但點分樹保證樹高為logn啊!我們每一次從點分樹的根開始,假設當前走到的點為now,那麼每次詢問從now到x的路徑,假設返回點為y,y一定是以now為分治中心時,now的一個子節點,而x一定是在y這個子樹所表示的聯通塊中,我們只要下一次將now變為y這棵子樹的分治中心再尋找就能使查詢範圍減小至少一半。因為時間複雜度上限不在查詢上,所以對於每一次查詢y這棵子樹的分治中心可以直接在點分樹上從y暴力往上爬,直到爬到點的父節點是now為止,這樣可以減少儲存的資訊量(方便後邊的重構)。如果對於一次詢問返回點為黑點,那麼需要將這個點插入到點分樹中,我們直接將這個點連到當前now的下面即可。這樣建出的點分樹會很不平衡,我們像
#include"rts.h" #include<map> #include<cmath> #include<vector> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; int tot; int vis[300010]; int head[300010]; int to[600010]; int next[600010]; int f[300010]; int size[300010]; int mx[300010]; int root; int num; int id[300010]; int col[300010]; int l,r; int now; int point; int rot; vector<int>q[300010]; inline void add(int x,int y) { next[++tot]=head[x]; head[x]=tot; to[tot]=y; } void getroot(int x,int fa) { size[x]=1; mx[x]=0; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { getroot(to[i],x); size[x]+=size[to[i]]; mx[x]=max(mx[x],size[to[i]]); } } mx[x]=max(num-size[x],mx[x]); if(mx[x]<mx[root]) { root=x; } } void dfs(int x,int fa,int rt) { q[rt].push_back(x); size[x]=1; for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]&&to[i]!=fa) { dfs(to[i],x,rt); size[x]+=size[to[i]]; } } } void partation(int x,int fa) { vis[x]=1; f[x]=fa; q[x].clear(); dfs(x,0,x); for(int i=head[x];i;i=next[i]) { if(!vis[to[i]]) { num=size[to[i]]; root=0; getroot(to[i],0); partation(root,x); } } } inline void insert(int x,int fa) { point=-1; f[x]=fa; vis[x]=1; for(int i=x;i;i=f[i]) { q[i].push_back(x); size[i]++; if(f[i]&&size[i]*100>(size[f[i]]+1)*90) { point=f[i]; } } if(point!=-1) { int len=q[point].size(); for(int i=0;i<len;i++) { vis[q[point][i]]=0; } num=size[point]; root=0; getroot(point,0); if(point==rot) { rot=root; } partation(root,f[point]); } } void play(int n,int T,int type) { for(int i=2;i<=n;i++) { id[i]=i; } srand(12345678); random_shuffle(id+2,id+n+1); col[1]=1; size[1]=1; vis[1]=1; q[1].push_back(1); mx[0]=1<<30; if(type==3) { random_shuffle(id+2,id+n+1); l=r=1; for(int i=2;i<=n;i++) { if(col[id[i]]) { continue; } if(col[explore(l,id[i])]) { while(!col[id[i]]) { col[now=explore(r,id[i])]=1; r=now; } } else { while(!col[id[i]]) { col[now=explore(l,id[i])]=1; l=now; } } swap(l,r); } return ; } else { rot=1; for(int i=2;i<=n;i++) { now=rot; while(!col[id[i]]) { int num=explore(now,id[i]); if(col[num]) { while(f[num]!=now) { num=f[num]; } now=num; } else { add(now,num); add(num,now); col[num]=1; insert(num,now); now=num; } } } return ; } }