点分治
-
定义: 点分治,是一种针对可带权树上简单路径统计问题的算法 (无根树)
-
方法: 把无根树平均地分割成若干互不影响的子问题求解,极大降低了时间复杂度,是一种巧妙的暴力
-
树的重心:
①一棵树最少有一个重心,最多有两个重心,若有两个重心,则他们相邻(即连有直接边)。②树上所有点到某个点的距离和里,到重心的距离和最小;若有两个重心,则其距离和相同。
③若以重心为根,则所有子树的大小都不超过整棵树的一半。
④在一棵树上添加或删除一个叶子节点,其重心最多平移一条边的距离
⑤两棵树通过连一条边组合成新树,则新树重心在原来两棵树的重心的连线上。
-
核心:
- 随重心便利,计算根节点后删掉根节点,继续找子树的重心
- 把每个点的数值计算固定到当前子树的点数 O(sz)
-
重点代码
int vis[maxn], sz[maxn], wt[maxn], arr[maxn], num, n, k, root,Tisz; void getroot(int u, int fa) { wt[u] = 0; sz[u] = 1; for (int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to; if (!vis[v] && v != fa) { getroot(v, u); sz[u] += sz[v]; wt[u] = max(wt[u], sz[v]); } } wt[u] = max(wt[u], Tsiz - sz[u]); if (wt[root] > wt[u]) root = u; } void dfs(int u, int fa, int len) { arr[++num] = len; for (int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to, w = edge[i].w; if (v == fa || vis[v]) continue; dfs(v, u, len + w); } } void f(int u,int d,int flag) { num = 0; dfs(u, 0, d); sort(arr + 1, arr + num + 1); int tot = 0; b[++tot] = arr[1]; bot[tot] = 1; for (int i = 2;i <= num;i++) if (arr[i] == arr[i - 1]) bot[tot]++; else b[++tot] = arr[i], bot[tot] = 1; int l = 1, r = tot; while (l < r) { if (b[l] + b[r] > k) r--; else if (b[l] + b[r] < k) l++; else { if (flag) ans += bot[l] * bot[r]; else ans-= bot[l] * bot[r]; l++; } } for (int t = 1;t <= tot;t++) { if (k == 2 * b[t] && bot[t] >= 2) { if (flag) ans += (bot[t] - 1) * bot[t] / 2; else ans -= (bot[t] - 1) * bot[t] / 2; } } } void DFS(int u) { if (u == 0) return; vis[u] = 1; f(u, 0, 1); for (int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].to, w = edge[i].w; if (vis[v]) continue; f(v, w, 0); Tsiz = sz[v]; wt[root = 0] = inf; getroot(v, 0); DFS(root); } }