树的直径与重心
或许更好的阅读体验
树的直径求解方法一
思路
先选取一个点rt作为根节点,dfs去找到一个最长路径的点U,然后通过这个点去dfs,找到路径最长的点V,U->V就是这课树的直径。
证明正确性:
假如rt在直径上的话,最长路径的点U一定是直径的一个端点,这一点是显然的。
假如rt不在直径上,那么从这个点出发也一定可以找到一条路到达直径上一点,接下来就如同上面一样了,无非就是在真正的dis上再加上一段固定的value,对我们最后的直径端点查找并无影响。
代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N = 1e5 + 10;int head[N], to[N], value[N], nex[N], cnt;
int n, ans, dis[N];inline ll read() {ll x = 1, s = 0; char c;c = getchar();while(c < '0' || c > '9') {if(c == '-') x = -1;c = getchar();}while(c >= '0' && c <= '9') {s = (s << 1) + (s << 3) + (c ^ 48);c = getchar();}return x * s;
}void add(int x, int y, int w) {to[cnt] = y;nex[cnt] = head[x];value[cnt] = w;head[x] = cnt++;
}void dfs(int rt, int fa) {for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa) continue;dfs(to[i], rt);dis[to[i]] = dis[rt] + value[i];}
}int main() {n = read();int x, y, w;memset(head, -1, sizeof head);for(int i = 1; i < n; i++) {x = read(), y = read(), w = read();add(x, y, w);add(y, x, w);}dfs(1, 0);int u = 0, v = 0;for(int i = 1; i <= n; i++)if(dis[i] > dis[u]) u = i;memset(dis, 0, sizeof dis);dfs(u, 0);for(int i = 1; i <= n; i++)if(dis[i] > dis[v]) v = i;printf("%d %d %d\n", u, v, dis[v]);return 0;
}
树的直径求解方法二
思路
以根节点出发,去寻找到这个根节点的最长路,以及次长路,得到的两个节点就是树的直径的端点。如果我们要得到直径的数值,还应该要做一次dfs,因为我们一开始选定的根节点无法保证是不是在直径上。
代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N = 1e5 + 10;int head[N], to[N], value[N], nex[N], cnt;
int n, ans, fir[N], sec[N], firnode[N], secnode[N];inline ll read() {ll x = 1, s = 0; char c;c = getchar();while(c < '0' || c > '9') {if(c == '-') x = -1;c = getchar();}while(c >= '0' && c <= '9') {s = (s << 1) + (s << 3) + (c ^ 48);c = getchar();}return x * s;
}void add(int x, int y, int w) {to[cnt] = y;nex[cnt] = head[x];value[cnt] = w;head[x] = cnt++;
}void dfs(int rt, int fa) {firnode[rt] = secnode[rt] = rt;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa) continue;dfs(to[i], rt);if(fir[to[i]] + value[i] > fir[rt]) {//更新最长路sec[rt] = fir[rt];fir[rt] = fir[to[i]] + value[i];firnode[rt] = to[i];}else if(fir[to[i]] + value[i] > sec[rt]) {//更新次长路sec[rt] = fir[to[i]] + value[i];secnode[rt] = to[i];}}
}int main() {n = read();int x, y, w;memset(head, -1, sizeof head);for(int i = 1; i < n; i++) {x = read(), y = read(), w = read();add(x, y, w);add(y, x, w);}dfs(1, 0);printf("%d %d %d", firnode[1], secnode[1], fir[1] + sec[1]);//假定我们选择的点是直径上的点。return 0;
}
树的重心
思路
找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。
树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样,则这两个点都是重心
并且,一棵树最多有两个重心,且相邻。
因此我们可以通过dfs去记录每个节点的子树节点值,然后去统计其最大结点数最小的节点。
代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N = 1e5 + 10;int head[N], to[N], value[N], nex[N], cnt;
int n, ans, sz[N], maxn = 0x3f3f3f3f, heart;inline ll read() {ll x = 1, s = 0; char c;c = getchar();while(c < '0' || c > '9') {if(c == '-') x = -1;c = getchar();}while(c >= '0' && c <= '9') {s = (s << 1) + (s << 3) + (c ^ 48);c = getchar();}return x * s;
}void add(int x, int y, int w) {to[cnt] = y;nex[cnt] = head[x];value[cnt] = w;head[x] = cnt++;
}void dfs(int rt, int fa) {sz[rt] = 1;int now_maxn = 0;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa) continue;dfs(to[i], rt);sz[rt] += sz[to[i]];if(sz[to[i]] > now_maxn) now_maxn = sz[to[i]];//记录最大子节点。}if(n - sz[rt] > now_maxn) now_maxn = n - sz[rt];//其父节点也算是他的子节点if(now_maxn < maxn) heart = rt, maxn = now_maxn;
}int main() {n = read();int x, y, w;memset(head, -1, sizeof head);for(int i = 1; i < n; i++) {x = read(), y = read(), w = read();add(x, y, w);add(y, x, w);}dfs(1, 0);printf("%d\n", heart);return 0;
}