作业介绍
// 最后的路径一定是在直径上的
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
const int M = N * 2;
#define int long long
int n, s;
int head[N], ver[M], nxt[M], edge[M], tot;
int diameter;
bool flg[N]; // 标记点是不是直径上的点
int dis[N], last[N];
int start, ed;
int dis1[N]; // dis1[i] 从 i 出发,不经过直径上的点能到达的最远距离
void add(int u, int v, int w) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
edge[tot] = w;
}
void dfs(int u, int fa, int w) {
dis[u] = dis[fa] + w;
last[u] = fa;
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa)
continue;
int w = edge[i];
dfs(v, u, w);
}
}
// 从点 u 出发, 不经过直径上的点能走到的最远的距离
int ans = 0;
int maxdis(int u, int fa, int w) {
ans = max(ans, w);
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
int nx = edge[i];
if (flg[v])
continue; // 不经过直径上的点
if (v == fa)
continue;
maxdis(v, u, w + nx);
}
return ans;
}
signed main() {
cin >> n >> s;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w), add(v, u, w);
}
// 两DFS 求直径
dfs(1, 0, 0);
start = 1;
for (int i = 2; i <= n; i++)
if (dis[i] > dis[start])
start = i;
dfs(start, 0, 0);
ed = 1;
for (int i = 2; i <= n; i++)
if (dis[i] > dis[ed])
ed = i;
diameter = dis[ed];
// 标记直径上的点
for (int i = ed; i ; i = last[i])
flg[i] = 1;
// 计算 dis1 数组
for (int i = ed; i ; i = last[i])
dis1[i] = maxdis(i, 0, 0);
int ans = 1e9;
// 最后的一定是在直径上的
// 遍历直径的是从 ed 到 start
for (int l = ed, r = ed; r != start ; r = last[r]) {
// 保证 l 到 r 的区间距离是 小于等于 s
int t = l;
// 单调队列优化,自己尝试实现一下
while (dis[r] - dis[l] <= s && l)
t = l, l = last[l];
// t - r 的范围内,距离值是小于等于 s 的
int maxx = 0;
for (int j = r; j != t; j = last[j])
maxx = max(maxx, dis1[j]);
maxx = max({maxx, dis[t], dis[ed] - dis[r]});
ans = min(maxx, ans);
}
cout << ans;
return 0;
}
// 新建道路,然后新建的道路只能走一次
// 如果原来是树,每个道路都是肯定会走过一次
// 走过的数量为 2*(n-1)
// 新建的道路的数量是 1 或者 2
// k =1 的时候,我们直接连接直径上的 两个点,这样直径上的所有的点都只用经过一次(形成环形)
// 最后的结果为 2*(n-1) - 直径 + 1
// k =2 的时候
// 1. 建路形成的环无公共边: 2*(n-1) - 环1的长度 - 环2的长度 +2
// 2. 建路形成的环有公共边:
// 走的边数 = 2*(n-1) -(环1路边数 - 公共边) -(环2路边数 - 公共边) + 2(新建立的边)
// 选择环:建立的第一个环肯定是直径 ; 然后标记直径上的边全部标记为 -1
// 接着在求树的最长直径,
// 最后化简成: 走过的边 = 2*(n-1) - 直径1 - 直径2 + 2新边
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = N * 2;
int n, k;
int head[N], ver[M], nxt[M], tot;
int cnt, start, ed;
int dis[N], last[N];
int diameter1, diameter2;
bool diamerpath[N];
void add(int u, int v) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dfs(int u, int fa, int w) {
last[u] = fa;
dis[u] = dis[fa] + w;
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa) continue;
dfs(v, u, 1);
}
}
void dp(int u, int fa) {
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa) continue;
dp(v, u);
}
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa) continue;
int w = diamerpath[u] && diamerpath[v] ? -1 : 1;
diameter2 = max(diameter2, dis[u] + dis[v] + w);
dis[u] = max(dis[u], dis[v] + w);
}
}
void road() {
dfs(1, 0, 0);
start = 1;
for (int i = 2 ; i <= n; i++)
if (dis[i] > dis[start])
start = i;
dfs(start, 0, 0);
ed = 1;
for (int i = 2; i <= n; i++) {
if (dis[i] > dis[ed])
ed = i;
}
diameter1 = dis[ed]; // 求出最长直径
}
int main() {
cin >> n >> k;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
road();
if (k == 1)
cout << 2 * (n - 1) - diameter1 + 1;
else {
for (int i = ed; i ; i = last[i])
diamerpath[i] = 1;
memset(dis, 0, sizeof dis);
dp(1, 0);
cout << 2 * (n - 1) - diameter1 - diameter2 + 2;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int M = N * 2;
#define int long long
int n;
int head[N], ver[M], nxt[M], edge[M], tot;
int diameter;
bool flg[N]; // 标记点是不是直径上的点
int dis[N], last[N];
int start, ed;
void add(int u, int v, int w) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
edge[tot] = w;
}
void dfs(int u, int fa, int w) {
dis[u] = dis[fa] + w;
last[u] = fa;
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa)
continue;
int w = edge[i];
dfs(v, u, w);
}
}
// 从点 u 出发, 不经过直径上的点能走到的最远的距离
int ans = 0;
int maxdis(int u, int fa, int w) {
ans = max(ans, w);
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
int nx = edge[i];
if (flg[v])
continue; // 不经过直径上的点
if (v == fa)
continue;
maxdis(v, u, w + nx);
}
return ans;
}
signed main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w), add(v, u, w);
}
// 两DFS 求直径
dfs(1, 0, 0);
start = 1;
for (int i = 2; i <= n; i++)
if (dis[i] > dis[start])
start = i;
dfs(start, 0, 0);
ed = 1;
for (int i = 2; i <= n; i++)
if (dis[i] > dis[ed])
ed = i;
diameter = dis[ed];
cout << diameter << endl;
// 标记一下直径上所有的点
for (int i = ed ; i ; i = last[i])
flg[i] = 1;
int l = start, r = ed;
for (int i = ed; i ; i = last[i]) {
ans = 0;
if (dis[ed] - dis[i] == maxdis(i, 0, 0))
r = i;
if ( dis[i] == maxdis(i, 0, 0) && l == start)
l = i;
}
// 求出公共边的数量
int common = 0;
for (int i = r; i != l ; i = last[i])
common++;
cout << common;
return 0;
}
// k =1 2*(n-1) -直径 +1
// k = 2 的时候
// 1. 求出直径1 的长度
// 2. 直径上所有边标记为 -1,标记点,flg[u]=1 && flg[v]=1
// 3. 再去求直径 2 的长度
// res = 2*(n-1)-直径1-直径2 + 2
// 并查集
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
const int M = N * 2;
int n, m, q;
int head[N], ver[M], nxt[M], tot;
int fa[N];
void add(int u, int v) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
int find(int x) { // 并查集
if (x == fa[x])
return x;
return fa[x] = find(fa[x]);
}
bool vis[N];
int diameter[N]; // 直径长度
int d[N]; // d[x] 从 x 出发能到达的最远的点的距离
int ans;
void dp(int x) { // 树形 DP 求直径长度
vis[x] = 1;
for (int i = head[x] ; i ; i = nxt[i]) {
int y = ver[i];
if (vis[y])
continue;
dp(y);
ans = max(ans, d[x] + d[y] + 1);
d[x] = max(d[x], d[y] + 1);
}
diameter[x] = ans;
}
int main() {
cin >> n >> m >> q;
// 并查集初始化
for (int i = 1 ; i <= n; i++)
fa[i] = i;
for (int i = 1 ; i <= m; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
u = find(u), v = find(v);
fa[v] = u;
}
// 求所有树的直径,只需要对根求直径、 fa[i] =i
for (int i = 1 ; i <= n; i++) {
if (i == fa[i])
ans = 0, dp(i);
}
while (q--) {
int opt, x, y;
cin >> opt >> x;
if (opt == 1) {
x = find(x);
cout << diameter[x] << endl;
} else {
cin >> y;
x = find(x), y = find(y);
if (x == y)
continue;
else {
fa[x] = y;
diameter[y] = max({diameter[x], diameter[y], (diameter[x] + 1) / 2 + (diameter[y] + 1) / 2 + 1});
}
}
}
return 0;
}
直径参考代码1

// 求树的直径
// 输出直径上的点
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = N * 2;
int n;
int head[N], ver[M], nxt[M], tot;
int dis[N];
int start, ed;
int last[N]; // 记录直径
void add(int u, int v) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dfs(int u, int fa, int flg) {
dis[u] = dis[fa] + 1;
if (flg == 2)
last[u] = fa;
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa)
continue;
dfs(v, u, flg);
}
}
int main() {
cin >> n;
for (int i = 1 ; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs(1, 0, 1);
start = 1;
for (int i = 2; i <= n; i++)
if (dis[i] > dis[start])
start = i;
dis[0] = -1;
dfs(start, 0, 2);
ed = 1;
for (int i = 2 ; i <= n; i++)
if (dis[i] > dis[ed])
ed = i;
cout << dis[ed] << endl;
// 直径上的点
for (int i = ed; i ; i = last[i])
cout << i << " ";
return 0;
}
M
// 如果是有唯一的重心,
// 随便删除一条边,再加入这条边
// 如果有两个重心,那么删除其中一个重心的一个叶子边
// 然后将这个叶子直接连接到最后保留的重心上
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = N * 2;
int T, n;
int head[N], ver[M], nxt[M], tot;
vector<int> center;
int siz[N], maxsub[N];
int leaffather, leaf; // 要删除的叶子和叶子节点的父亲
void add(int u, int v) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dfs(int u, int fa) {
siz[u] = 1;
maxsub[u] = 0;
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if ( v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
maxsub[u] = max(maxsub[u], siz[v]);
}
maxsub[u] = max(maxsub[u], n - siz[u]);
}
void centercnt() {
for (int i = 1 ; i <= n; i++) {
if (maxsub[i] <= n / 2)
center.push_back(i);
}
}
// 找到一个叶子节点和一个他的父节点
void find(int u, int fa) {
for (int i = head[u] ; i ; i = nxt[i]) {
int v = ver[i];
if (v == fa) continue;
find(v, u);
return ;
}
leaf = u;
leaffather = fa;
}
int main() {
cin >> T;
while (T--) {
cin >> n;
memset(head, 0, sizeof head);
memset(siz, 0, sizeof siz);
memset(maxsub, 0, sizeof maxsub);
tot = 0;
center.clear();
for (int i = 1 ; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs(1, 0);
centercnt(); // 数一下重心的数量
if (center.size() == 1) {
cout << center[0] << " " << ver[head[center[0]]] << endl;
cout << center[0] << " " << ver[head[center[0]]] << endl;
} else { // 如果有两个重心,一个作为父节点一个作为子节点
find(center[0], center[1]);
cout << leaffather << " " << leaf << endl;
cout << center[1] << " " << leaf << endl;
}
}
return 0;
}
DFS order
DFS 树
要知道每个节点在 DFS 顺序中
可能出现的最小位置和最大位置
最小位置:其实就是深度 dep
最大位置:n - siz[x] +1
最远点对
直径的定义:相距最远的两个点的距离
f[x][2] : f[x][0],距离 x 最远的白点的距离
f[x][1] 距离 x 最远的黑点的距离
只有一个点, 初始化
x 是黑点 f[x][1] =0 f[x][0] = -1e9
x 是白点 f[x][1] = -1e9 f[x][0] = 1
ans = max({ans , f[x][1] + f[y][0] + 1 , f[x][0] +f[y][1] + 1})
f[x][1] = max(f[x][1] , f[y][1] +1) , x 后面接了 y
f[x][0] = max(f[x][0] , f[y][0] +1)
// 树的直径 DP 求法
// 树上DP
// d[x] 从节点 x 出发走向以 x 为根的子树,能够到达的最远节点的距离
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = N * 2;
int n;
int head[N], ver[M], nxt[M], tot;
int d[N], ans;
bool vis[N];
void add(int u, int v) {
tot++;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dp(int x) {
vis[x] = 1;
for (int i = head[x] ; i ; i = nxt[i]) {
int v = ver[i];
if (vis[v]) continue; // 必然不会经过之前经过的点
dp(v);
ans = max(ans, d[x] + d[v] + 1);
d[x] = max(d[x], d[v] + 1);
}
}
int main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dp(1);
cout << ans;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 10;
const int M = N * 2;
int n, pos, ans = 1e9;
int head[N], ver[M], nxt[M], tot;
bool v[N];
int size[N];
void add(int u, int v) {
++tot;
ver[tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
// 树的重心
void dfs(int x) {
v[x] = 1, size[x] = 1;
int max_part = 0;
for (int i = head[x] ; i ; i = nxt[i]) {
int y = ver[i];
if (v[y])
continue;
dfs(y);
size[x] += size[y];
max_part = max(max_part, size[y]);
}
max_part = max(max_part, n - size[x]);
//
if (max_part < ans || (max_part == ans && pos > x)) {
ans = max_part; // 全局变量 ans 记录中心对应的 max_part
pos = x; // 全局变量 pos 记录了重心
}
}
int res = 0;
int dep[N];
void dfs1(int x, int fa) {
dep[x] = dep[fa] + 1;
res += dep[x];
for (int i = head[x] ; i ; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
dfs1(y, x);
}
}
int main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
dfs(1);
dep[0] = -1;
dfs1(pos, 0);
cout << pos << " " << res;
return 0;
}
// 树的重心, 带点权
void dfs(int x) {
v[x] = 1, size[x] = c[x]; // 带上点权
int max_part = 0;
for (int i = head[x] ; i ; i = nxt[i]) {
int y = ver[i];
if (v[y])
continue;
dfs(y);
size[x] += size[y];
max_part = max(max_part, size[y]);
}
max_part = max(max_part, n - size[x]);
//
if (max_part < ans || (max_part == ans && pos > x)) {
ans = max_part; // 全局变量 ans 记录中心对应的 max_part
pos = x; // 全局变量 pos 记录了重心
}
}
int res = 0;
int dis[N];
void dfs1(int x, int fa, int w) {
dis[x] = dis[fa] + w;
res += dis[x];
for (int i = head[x] ; i ; i = nxt[i]) {
int y = ver[i];
if (y == fa)
continue;
dfs1(y, x,edge[i]);
}
}
// 重心, 带点权
// 计算每个点到重心的距离
// res += c[i]*dis[i];
题目
认领作业后才可以查看作业内容。
- 状态
- 正在进行…
- 题目
- 21
- 开始时间
- 2026-5-13 0:00
- 截止时间
- 2026-6-30 23:59
- 可延期
- 24 小时