兩個點之間的距離無非就是兩種關係:咱們約定\(dis[i]\)表示這個點到當前根節點的距離c++
樹上點分治的思想就是經過改變根節點從而轉化任意兩點的距離爲在同一個根節點下的狀況。算法
舉個例子數組
當咱們選定1號節點做爲咱們的根節點時,咱們能夠簡單的獲得(三號節點的子樹上的點到節點1, 4, 2, 7的距離,也就是不在三號節點子樹上的點的距離)(4, 2子樹同理)。優化
經過這一步轉換咱們只須要獲得三號節點子樹上的點之間的距離便可,這就是分治思想的體現,咱們能夠不斷地遞歸最後只剩一個節點,這個節點的子樹上的點到其子樹上的點的距離就是肯定的了,就是0嘛,只多是它本身到它本身。spa
因此簡而言之,點分治就是去不斷地遞歸某個節點地子樹,知道沒有子樹。3d
假如咱們的點是鏈接成一串的,咱們能任選一個點去當初始節點的子樹嗎?code
這裏顯然是不能的,當咱們選定的節點恰好是端點的時候,這個時候複雜度將會變成\(n^2\),這徹底違背了咱們優化其的初衷。blog
因而這裏有一個簡單的優化方法,就是每次咱們選取每顆子樹的重心去充當根節點,這樣的分治效果顯然是最優的。遞歸
因而咱們的樹上點分治算法好像已近逐漸能夠寫出來了,咱們經過下面這個例子來更加理解一下實現過程吧。get
/* 樹上點分治 */ #include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; const int N = 1e5 + 10; int head[N], to[N << 1], nex[N << 1], value[N << 1], cnt = 1; int sz[N], maxsz[N], dis[N], pre[N], vis[N], judge[10000010], is_true[110], query[110], q[N]; int n, m, sum, root; inline int read() { int f = 1, x = 0; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return f * x; } void get_root(int rt, int fa) {//簡單的找重心 sz[rt] = 1, maxsz[rt] = 0; for(int i = head[rt]; i; i = nex[i]) { if(vis[to[i]] || to[i] == fa) continue;//加了一個vis判斷,防止跑到已經訪問過的根節點給上去。 get_root(to[i], rt); maxsz[rt] = max(maxsz[rt], sz[to[i]]); sz[rt] += sz[to[i]]; } maxsz[rt] = max(maxsz[rt], sum - sz[rt]); if(maxsz[rt] < maxsz[root]) root = rt; } void get_dis(int rt, int fa) {//就是dfs樹上最短路的實現過程。 pre[++pre[0]] = dis[rt];//記錄其子樹的每一個節點到根節點的距離。 for(int i = head[rt]; i; i = nex[i]) { if(to[i] == fa || vis[to[i]]) continue; dis[to[i]] = dis[rt] + value[i]; get_dis(to[i], rt); } } void calc(int rt) {//核心。 int p = 0; for(int i = head[rt]; i; i = nex[i]) { if(vis[to[i]]) continue;//一樣的也是訪問子樹。 dis[to[i]] = value[i];//這裏必定要記得重置。 pre[0] = 0; get_dis(to[i], rt); for(int j = 1; j <= pre[0]; j++)//查詢有沒有點到當前子樹的點的距離是符合query中的要求的。 for(int k = 1; k <= m; k++) if(query[k] >= pre[j]) is_true[k] |= judge[query[k] - pre[j]]; for(int j = 1; j <= pre[0]; j++)//記錄咱們judge中被標記的點,方便在下一次分治以前重置。 if(pre[j] <= 1e7 + 5)//特判一下吧,題目的dis可能會到1e8,爲了防止數組越界, q[++p] = pre[j], judge[pre[j]] = 1; } for(int i = 1; i <= p; i++)//不用memset重置,防止變成n^2的算法。 judge[q[i]] = 0; } void solve(int rt) { vis[rt] = judge[0] = 1;//置這個點被訪問過,防止其子樹上的點再次訪問這個點。 calc(rt); for(int i = head[rt]; i; i = nex[i]) { if(vis[to[i]]) continue;//咱們確定是找一個沒有訪問的子樹上的點去進行下一次分治遞歸。 sum = sz[to[i]], root = 0; maxsz[root] = INF; get_root(to[i], 0); solve(root); } } void add(int x, int y, int w) { to[cnt] = y; nex[cnt] = head[x]; value[cnt] = w; head[x] = cnt++; } int main() { // freopen("in.txt", "r", stdin); n = read(), m = read(); int x, y, w; for(int i = 1; i < n; i++) {//雙向建邊。 x = read(), y = read(), w = read(); add(x, y, w); add(y, x, w); } for(int i = 1; i <= m; i++) query[i] = read(); root = 0;//尋找初始的遞歸根節點。 maxsz[root] = INF; get_root(1, 0); solve(root); for(int i = 1; i <= m; i++) puts(is_true[i] ? "AYE" : "NAY"); return 0; }