题单
树的直径学习笔记
P4408 [NOI2003] 逃学的小孩
思路
由两地之间的路径唯一可知,这n个地方(点)连同他们之间的路(边)构成一棵无根树。由题意可知,即是寻找三点A、B、C,在满足CB > CA的前提下,使得CA + AB最长。我们知道,树的直径是树的最长路径。于是我们对于A、B是否在直径上分类讨论可得,那两个小朋友的家(A、B)一定分别是直径的两个端点。于是,我们只需要对于直径上除端点外的点D,找其向外延申的最远点C,于是答案max{2 * min (AD, DB) + CD + AB}。时间复杂度为O(n)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int n, m;
long long ans;
int head[maxn], cnt;
struct edge
{
int v, w, next;
} e[maxn << 1];
void add (int u, int v, int w)
{
e[++ cnt] = (edge) {v, w, head[u]};
head[u] = cnt;
}
int start_point, last_point, f[maxn], vis[maxn];
long long dis[maxn], tree_d, maxl;
void dfs (int u, int fa, int tp)
{
if (tp == 1) f[u] = fa;
if (dis[u] > tree_d) tree_d = dis[u], last_point = u;
int v, w;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa) continue;
dis[v] = dis[u] + w;
dfs (v, u, tp);
}
}
long long disi[maxn];
void dfsi (int u, int fa) // 寻找直径上除端点外的某个点向直径外延申的最长长度
{
if (disi[u] > maxl) maxl = disi[u];
int v, w;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa || vis[v]) continue; // 如果v是u的父亲后v是直径上的点直接跳即可
disi[v] = disi[u] + w;
dfsi (v, u);
}
}
int main ()
{
scanf ("%d %d", &n, &m);
int u, v, w;
for (int i = 1; i <= m; i++)
{
scanf ("%d %d %d", &u, &v, &w);
add (u, v, w), add (v, u, w);
}
dfs (1, 0, 0);
memset (dis, 0, sizeof (dis));
start_point = last_point;
tree_d = 0, last_point = 0;
dfs (start_point, 0, 1);
int point = last_point;
while (point) vis[point] = 1, point = f[point]; // 给直径上的点打上标记
for (int point = last_point; point; point = f[point])
{
memset (disi, 0, sizeof (disi));
maxl = 0;
dfsi (point, f[point]); 找所谓的CD
if (dis[point] >= dis[last_point] - dis[point]) // 判断DA和DB那一段更长。
ans = max (ans, maxl + 2 * dis[last_point] - dis[point]);
else ans = max (ans, maxl + dis[point] + dis[last_point]);
}
printf ("%lld", ans);
return 0;
}
收获
①知道了如何求直径除端点外的点向外延申的最长距离(代码的dfsi函数和vis数组打标记操作)
P1099 [NOIP2007 提高组] 树网的核
思路
根据题意,这个题即是在某直径上找一段长度不超过s路径(该路径两端均为树网中的结点),找出这条路径上的点向外延申的最大距离A,然后取这条直径上所有长度不超过s的路径达到的A的最大值B,然后取所有直径B的最大值即为答案ans。然后我们发现我们不需要找出所有的直径,因为我们取的不是直径1,而是直径2,则我们在原来取的直径1中的肯定存在一个点k使得在包括这个点的以后的点不被取到,而换成了与这些点组成的路径长度相等的直径2上对应的路径,无论取直径1还是2,这都不影响最后从k的祖先向外延申的最大距离,因为k的祖先向外延申的最大距离是直径1剩下的路径长度,这显然肯定比其他任何在k点以后得到的长度大(或等于)。那我们现在把注意力放在一条直径上,路径端点到与它较近的直径端点的距离肯定大于路径端点向路径外延申的距离,故对于树上其他点到路径端点的最大距离肯定是路径端点到与它较近的直径端点的距离。对于每个可取到的不超过s的路径,我们找li = max(dis[i], dis[lp] - dis[j]),其中i是靠近直径起点(搜索直径时起点的)的那个路径端点,j时靠近结束点的那个路径端点;然后我们对于路径上的非端点,它向直径外延申的距离lii即为所求,于是这条路径的偏心距E就是max (max {所有的lii}, li)。最小的偏心距就是min{所有的E}。时间复杂度为O(n)
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 310;
int n, s;
int head[maxn], cnt, ans;
struct edge
{
int v, w, next;
} e[maxn << 1];
void add (int u, int v, int w)
{
e[++ cnt] = (edge) {v, w, head[u]};
head[u] = cnt;
}
int dis[maxn], f[maxn], last_point, tree_d, start_point;
void dfs (int u, int fa, int tp)
{
if (tp == 1) f[u] = fa;
if (dis[u] > tree_d) tree_d = dis[u], last_point = u;
int v, w;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa) continue;
dis[v] = dis[u] + w;
dfs (v, u, tp);
}
}
int vis[maxn], disi[maxn], maxl[maxn];
void dfsi (int u, int fa, int k)
{
if (maxl[k] < disi[u]) maxl[k] = disi[u];
int v, w;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa || vis[v]) continue;
disi[v] = disi[u] + w;
dfsi (v, u, k);
}
}
int main ()
{
scanf ("%d %d", &n, &s);
int u, v, w;
for (int i = 1; i < n; i++)
{
scanf ("%d %d %d", &u, &v, &w);
add (u, v, w), add (v, u, w);
}
dfs (1, 0, 0);
start_point = last_point;
memset (dis, 0, sizeof (dis));
last_point = tree_d = 0;
dfs (start_point, 0, 1);
for (int i = last_point; i; i = f[i])
vis[i] = 1;
for (int i = last_point; i; i = f[i])
dfsi (i, f[i], i);
ans = 2147483647;
for (int i = last_point; i; i = f[i])
{
int j = i, tot = 0;
while (j && dis[i] - dis[f[j]] <= s) j = f[j], tot = max (tot, maxl[j]);
tot = max (tot, max (tree_d - dis[i], dis[j]));
ans = min (ans, tot);
}
printf ("%d", ans);
return 0;
}
收获
①知道了如何得到某条直径上有哪些点
②知道了可以利用树的直径上的路径的端点到较近的那个直径端点的距离比这个路径端点向直径外延申的距离要大或等于。
P5536 【XR-3】核心城市
思路
这个题我们主要用了贪心的思想。首先我们先讨论只有一个核心城市的情况,这是我们不难想到这个核心城市应该在直径的中点,因为若不然,则一定存在某个点使得起到核心城市的距离大于直径的一半,故若只有一个核心城市那么这个城市一定在直径的中点。然后我们讨论,有多个核心城市的情况,我们易得直径的重点一定在这些相互联通的核心城市中,原因也是否则会有距离大于直径的一半的非核心城市。于是我们先定下一个直径中点这个核心城市,然后我们以直径中点向外拓展,找到最优的核心城市群。那么什么是最优的核心城市群呢?那最优的肯定是每拓展一个城市,都可使得目前的非核心城市到核心城市的最远距离减小。为了达到这个目的,我们可以以直径的中点为树的根节点,生成一个树,用depth[i]表示点i的深度,用maxdepth[i]表示点i能延申到的最大深度,于是若这个点为加进核心城市群去,则非核心城市到达这个点的最远距离dis[i]即为maxdepth[i]-depth[i]。那如果这点点加进去要对非核心城市到核心城市的最远距离减小这个目标有贡献,则需要dis大于dis[i]的所有的其他点全被加入核心城市群中,故我们把dis由大到小排序,则前k名即是我们的核心城市圈,故dis[k+ 1]即为所求。时间复杂度为O(n)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, k;
int head[maxn], cnt;
struct edge
{
int v, w, next;
} e[maxn << 1];
void add (int u, int v)
{
e[++ cnt] = (edge) {v, 1, head[u]};
head[u] = cnt;
}
int f[maxn], dis[maxn], lp, dm; // lp : last point; dm : dis_max
void dfs (int u, int fa, int tp)
{
if (tp == 1)
f[u] = fa;
if (dis[u] > dm)
dm = dis[u], lp = u;
int v, w;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa) continue;
dis[v] = dis[u] + w;
dfs (v, u, tp);
}
}
int deep[maxn], maxdeep[maxn], deldeep[maxn];
void dfsi (int u, int fa)
{
maxdeep[u] = deep[u] = deep[fa] + 1; // 初始化maxdeep[u],对deep[u]赋值。
int v, w;
for (int i = head[u]; i ; i = e[i].next)
{
v = e[i].v, w = e[i].w;
if (v == fa) continue;
dfsi (v, u);
maxdeep[u] = max (maxdeep[u], maxdeep[v]); // 在搜索完u的某个子节点v后更新maxdeep[u]
}
}
bool cmp (int a, int b)
{
return a > b;
}
int main ()
{
scanf ("%d %d", &n, &k);
int u, v;
for (int i = 1; i <= n - 1; i++)
scanf ("%d %d", &u, &v),
add (u, v), add (v, u);
dfs (1, 0, 0);
dm = 0;
memset (dis, 0, sizeof (dis));
dfs (lp, 0, 1);
// 找中点
for (int i = 1; i <= dm / 2; i++)
lp = f[lp];
dfsi (lp, 0);
for (int i = 1; i <= n; i++)
deldeep[i] = maxdeep[i] - deep[i] + 1;
sort (deldeep + 1, deldeep + n + 1, cmp);
printf ("%d", deldeep[k + 1]);
return 0;
}
收获
①知道了如何得到一个节点可能能达到的最大深度。