树的直径与重心
树的直径
1、两种方法解决:一种是通过两次 \(dfs\) 遍历得到最大直径,一种是通过树形 \(dp\) 求出每个节点中子树节点到此节点的最大距离和次大距离。
2、两种时间复杂度都是 \(O(n)\),空间复杂度也都是 \(O(n)\)。
3、但是前者方法只能处理非负边权或非负点权的直径,对于有负权的树我们就得使用第二种方法了。
4、模版:洛谷B4016。
方法一:两次 \(dfs\)
template<typename T>
struct diameter{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
int n, m, cnt, be1, be2;// 某条直径的起点和终点
T dia;// 最长直径长度
vector<node> edg;
vector<int> head;
vector<T> dis;
diameter() {}
diameter(int n_, int m_) : edg(m_ << 1 | 1), head(n_ + 1), dis(n_ + 1) {
cnt = 0;
dia = 0;
n = n_;
m = m_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_) {
n = n_;
m = m_;
cnt = 0;
dia = 0;
edg.assign(m_ << 1 | 1, node());
head.assign(n_ + 1, 0);
dis.assign(n_ + 1, 0);
}
inline void dfs(int u, int fa) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == fa) {
continue;
}
dis[v] = dis[u] + w;
dfs(v, u);
}
}
inline void get() {
T ans = 0;
dfs(1, 0);
for (int i = 1; i <= n; i++) {
if (dis[i] > ans) {
ans = dis[i];
be1 = i;
}
}
dis[be1] = 0;
dfs(be1, 0);
ans = 0;
for (int i = 1; i <= n; i++) {
if (dis[i] > ans) {
ans = dis[i];
be2 = i;
}
}
dia = ans;
}
};
void solve() {
int n;
cin >> n;
diameter<int> d(n, n - 1);
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
d.add(u, v, 1);
d.add(v, u, 1);
}
d.get();
cout << d.dia << '\n';
}
方法二:树形 \(dp\)
template<typename T>
struct diameter{
const T mn = -mof;
struct node {
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = -mof) : to(to), nxt(nxt), w(w) {}
};
int n, m, cnt;
T dia;
vector<node> edg;
vector<int> head;
vector<T> dmx, dcmx;
diameter() {}
diameter(int n_, int m_) : edg(m_ << 1 | 1), head(n_ + 1), dmx(n_ + 1, mn), dcmx(n_ + 1, mn) {
cnt = 0;
dia = mn;
n = n_;
m = m_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_) {
cnt = 0;
dia = mn;
n = n_;
m = m_;
edg.assign(m_ << 1 | 1, node());
head.assign(n_ + 1, 0);
dmx.assign(n_ + 1, mn);
dcmx.assign(n_ + 1, mn);
}
inline void dfs(int u, int fa) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == fa) {
continue;
}
dfs(v, u);
T t = w + (dmx[v] == mn ? 0 : dmx[v]);
if (t > dmx[u]) {
dcmx[u] = dmx[u];
dmx[u] = t;
} else if (t > dcmx[u]) {
dcmx[u] = t;
}
}
dia = max(dia, (T)((dmx[u] == mn ? 0 : dmx[u]) + (dcmx[u] == mn ? 0 : dcmx[u])));
}
};
void solve() {
int n;
cin >> n;
diameter<int> d(n, n - 1);
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
d.add(u, v, 1);
d.add(v, u, 1);
}
d.dfs(1, 0);
cout << d.dia << '\n';
}
树的重心
1、性质:
-
树至多有两个重心。如果树有两个重心,那么它们相邻。此时树一定有偶数个节点,且可以被划分为两个大小相等的分支,每个分支各自包含一个重心。
-
某个点是树的重心等价于它最大子树大小不大于整棵树大小的一半。
-
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。反过来,距离和最小的点一定是重心。
-
往树上增加或减少一个叶子,如果原节点数是奇数,那么重心可能增加一个,原重心仍是重心;如果原节点数是偶数,重心可能减少一个,另一个重心仍是重心。
-
把两棵树通过一条边相连得到一棵新的树,则新的重心在较大的一棵树一侧的连接点与原重心之间的简单路径上。如果两棵树大小一样,则重心就是两个连接点。
2、时间复杂度:\(O(n)\),空间复杂度:\(O(n)\)。
template<typename T>
struct Weight{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) {}
};
int n, m, cnt;
vector<int> st;
vector<int> head;
vector<T> siz, siz_mx;// 一个代表以此点为根的子树大小,另一个表示此点的子树大小的最大值
vector<node> edg;
Weight() {}
Weight(int n_, int m_) : head(n_ + 1), siz(n_ + 1, 1), siz_mx(n_ + 1), edg(m_ << 1 | 1) {
cnt = 0;
n = n_;
m = m_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_) {
cnt = 0;
n = n_;
m = m_;
head.assign(n_ + 1, 0);
siz.assign(n_ + 1, 0);
siz_mx.assign(n_ + 1, 0);
edg.assign(m_ << 1 | 1, node());
}
inline void dfs(int u, int fa) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == fa) {
continue;
}
dfs(v, u);
siz[u] += siz[v];
siz_mx[u] = max(siz_mx[u], siz[v]);
}
siz_mx[u] = max(siz_mx[u], n - siz[u]);
}
inline void get_weight() {
dfs(1, 0);
T ans = mof;
for (int i = 1; i <= n; i++) {
if (siz_mx[i] < ans) {
ans = siz_mx[i];
st.clear();// 找到更小的了,就把之前st里面存的比他大的点都删除掉
st.push_back(i);
} else if (siz_mx[i] == ans) {
st.push_back(i);
}
}
}
};
void solve() {
int n;
std::cin >> n;
Weight<int> ans(n, n - 1);
ans.get_weight();
}
拓展:我们还可以求出以某个节点为根的所有子树的重心
1、时间复杂度:\(O(n)\),空间复杂度:\(O(n)\)。
2、模版:CF685B。
3、利用的是树的重心的最后一条性质:把两棵树通过一条边相连得到一棵新的树,则新的重心在较大的一棵树一侧的连接点与原重心之间的简单路径上。如果两棵树大小一样,则重心就是两个连接点。
template<typename T>
struct Weight{
struct node{
int to, nxt;
T w;
node(int to = 0, int nxt = 0, T w = 0) : to(to), nxt(nxt), w(w) {}
};
int n, m, cnt;
vector<int> st;
vector<int> head;
vector<int> fa, id;
vector<T> siz, siz_mx;
vector<node> edg;
Weight() {}
Weight(int n_, int m_) : head(n_ + 1), id(n_ + 1), fa(n_ + 1), siz(n_ + 1, 1), siz_mx(n_ + 1), edg(m_ << 1 | 1) {
cnt = 0;
n = n_;
m = m_;
}
inline void add(int u, int v, const T &w) {
++cnt;
edg[cnt].to = v;
edg[cnt].nxt = head[u];
edg[cnt].w = w;
head[u] = cnt;
}
inline void init(int n_, int m_) {
cnt = 0;
n = n_;
m = m_;
head.assign(n_ + 1, 0);
id.assign(n_ + 1, 0);
fa.assign(n_ + 1, 0);
siz.assign(n_ + 1, 1);
siz_mx.assign(n_ + 1, 0);
edg.assign(m_ << 1 | 1, node());
}
inline void dfs(int u, int pa) {
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
dfs(v, u);
fa[v] = u;
siz[u] += siz[v];
siz_mx[u] = max(siz_mx[u], siz[v]);
}
int id_ = u;
T mx = siz_mx[u];
for (int i = head[u]; i; i = edg[i].nxt) {
int v = edg[i].to;
T w = edg[i].w;
if (v == pa) {
continue;
}
int it = id[v], p = it;
T pre = max(siz_mx[it], siz[u] - siz[it]);
while (fa[it] != u) {
it = fa[it];
T nxt = max(siz_mx[it], siz[u] - siz[it]);
if (nxt <= pre) {
pre = nxt;
p = it;
} else {
break;
}
}
if (pre < mx) {
mx = pre;
id_ = p;
}
}
id[u] = id_;
}
};
void solve() {
int n, q;
std::cin >> n >> q;
Weight<int> ans(n, n - 1);
for (int i = 2; i <= n; i++) {
int u, v = i;
cin >> u;
ans.add(u, v, 1);
}
ans.dfs(1, 0);
for (int i = 1; i <= q; i++) {
int u;
std::cin >> u;
std::cout << ans.id[u] << '\n';
}
}

浙公网安备 33010602011771号