题面
在一个地区有 n 个村庄,编号为1,2,…,n。
有 n-1 条道路连接着这些村庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其他任一个村庄。
每条道路的长度均为1个单位。
为保证该地区的安全,巡警车每天都要到所有的道路上巡逻。
警察局设在编号为1的村庄里,每天巡警车总是从警局出发,最终又回到警局。
为了减少总的巡逻距离,该地区准备在这些村庄之间建立 K 条新的道路,每条新道路可以连接任意两个村庄。
两条新道路可以在同一个村庄会合或结束,甚至新道路可以是一个环。
因为资金有限,所以 K 只能为1或2。
同时,为了不浪费资金,每天巡警车必须经过新建的道路正好一次。
编写一个程序,在给定村庄间道路信息和需要新建的道路数的情况下,计算出最佳的新建道路的方案,使得总的巡逻距离最小。
输入格式
第一行包含两个整数 n 和 K。
接下来 n-1 行每行两个整数 a 和 b,表示村庄 a 和 b 之间有一条道路。
输出格式
输出一个整数,表示新建了 K 条道路后能达到的最小巡逻距离。
数据范围
3≤n≤100000,
1≤K≤2,
1≤a,b≤n
输入样例:
8 1
1 2
3 1
3 4
5 3
7 5
8 5
5 6
输出样例:
11
题解
先说一下主要思路吧
k = 1/ 2, 说白了就是造两个环
第一个很简单, 就是连接直径
第二条也是连接直径(第二大), 那问题来了, 如何消除已经连过的直径?
将所连过的直径权值又1改-1, 即可
相当于你在算这条边未直径时, 贡献值为-1
那为什么不应该是贡献值为 0 呢?
其实是, 当你第一次连接直径时, 原本要原路返回, 这条边会走两次, 你连接之后直走一次
第二次连接时, 这条边会由于你的连线非但没少走, 还会多走一边故, 贡献从 1 变为 -1(新建的边必须走一次)
基本思路如此
问题来了, 第二次改变完边的权值时, 你不能用 dfs 或 bfs 去求直径, 为什么呢?
看一组数据(WestTree造的)
10 2
6 10
5 3
6 4
7 3
8 3
6 1
1 9
2 1
6 3
按照思路走一遍, 你会发现
第二次求直径时, 第一次搜索, 随着你选取的点不同, 找出的直径中的某个端点也不同
你拿着这个端点再去求, 另一个端点, 你求的直径能对吗?
所以第二次求直径要dp
那为什么第一次不直接也用dp求呢?
搜索可以回溯啊!改权值好改
代码
#include <bits/stdc++.h>
#define all(n) (n).begin(), (n).end()
#define se second
#define fi first
#define pb push_back
#define mp make_pair
#define sqr(n) (n)*(n)
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define per(i,a,b) for(int i=a;i>=(b);--i)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef vector<int> VI;
typedef double db;
const int N = 100005;
int n, m, _, k;
bool v[N];
int h[N], ne[N << 1], co[N << 1], to[N << 1], tot;
int d[N], f[N], b[N];
void add(int u, int v, int c) {
to[++tot] = v; ne[tot] = h[u]; h[u] = tot; co[tot] = c;
}
void dfs(int u) {
v[u] = 1;
for (int i = h[u]; i; i = ne[i]) {
int y = to[i];
if (v[y]) continue;
f[y] = u; b[y] = i; d[y] = d[u] + co[i];
if (d[0] < d[y])
d[0] = d[y], f[0] = y;
dfs(y);
}
}
void work(int& p, int& q) {
memset(v, 0, sizeof v); d[0] = -N;
d[1] = 0; dfs(1); p = f[0];
memset(v, 0, sizeof v); d[0] = -N;
d[p] = 0; dfs(p); q = f[0];
}
void dpfind(int u, int& ans) {
v[u] = 1;
for (int i = h[u]; i; i = ne[i]) {
int y = to[i];
if (v[y]) continue;
dpfind(y, ans);
ans = max(ans, d[u] + d[y] + co[i]);
d[u] = max(d[u], d[y] + co[i]);
}
d[u] = max(d[u], 0);
ans = max(ans, d[u]);
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> k;
rep(i, 2, n) {
int u, v; cin >> u >> v;
add(u, v, 1); add(v, u, 1);
}
int p, q, ans = (n - 1) << 1;
work(p, q);
ans -= d[0] - 1;
if (k == 2) {
for (int i = q; i != p; i = f[i]) {
co[b[i]] = -1;
co[b[i] + ((b[i] & 1) ? 1 : -1)] = -1;
}
memset(d, 0xbf, sizeof d);
memset(v, 0, sizeof v);
int res = 0;
dpfind(p, res);
ans -= res - 1;
}
cout << ans;
return 0;
}
贴一个短一点的代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
struct edge { int t, c, w; };
int n, m, _;
int d[N], s1, s2;
vector<edge> h[N];
pair<int, int> pre[N];
void dfs(int x, int fa, int& s) {
for (int i = 0; i < h[x].size(); ++i) if (h[x][i].t != fa) {
d[h[x][i].t] = d[x] + h[x][i].c; pre[h[x][i].t] = { x, i };
if (d[0] < d[h[x][i].t]) d[0] = d[s = h[x][i].t];
dfs(h[x][i].t, x, s);
}
}
int work() {
d[0] = 0; d[1] = 0; dfs(1, 0, s1);
d[0] = 0; d[s1] = 0; pre[s1] = { 0, 0 }; dfs(s1, 0, s2);
for (auto i = pre[s2]; i.first; i = pre[i.first])
h[i.first][i.second].c = -1, h[h[i.first][i.second].t][h[i.first][i.second].w].c = -1;
return d[0];
}
void dp(int x, int fa, int& ans) {
for (auto &y : h[x]) if (y.t != fa) {
d[y.t] = 0; dp(y.t, x, ans);
ans = max(ans, d[x] + d[y.t] + y.c);
d[x] = max(d[x], d[y.t] + y.c);
}
ans = max(ans, d[x]);
}
int main() {
cin >> n >> m; long long ans = n - 1 << 1;
for (int i = 2; i <= n; ++i) {
int u, v; cin >> u >> v;
h[u].push_back({v, 1, h[v].size()}); h[v].push_back({u, 1, h[u].size() - 1});
}
ans -= work() - 1; if (m == 2) d[1] = s1 = 0, dp(1, 0, s1), ans -= s1 - 1;
cout << ans;
return 0;
}
主要是 修改为负权之后,dfs求直径的方法就不能得出正解了
$\%\%\%$
大佬请问为什么求第二直径时要从第一次求出直径的端点p开始。
第二次就是不能从第一次求出的直径端点求啊,我题解不是说了要dp去求吗
第一篇代码里,dp求解时是dpfind(p, res); 从p出发求第二次的直径。p不就是第一次直径的端点嘛
dp找直径,哪个点都可以的,我只是顺手用了p,用1也行的
对于这个数据:
12 2
1 2
2 3
3 4
4 5
4 9
5 6
6 7
7 8
9 10
10 11
11 12
以1为端点进行dp就会错,用p才是对的
我自己写的和大佬你的代码都是这样,以p为端点就是正确答案14,用1就会得到16
并且这个数据其他的题解都会得到错误答案,只有大佬你的才是对的
正确答案应该是13,出现这样的问题是因为 dp 求直径写丑了,这棵树有负边,所以直径有可能不是拼接出来的,反而是 $ans = max(ans, d_u)$,代码已修改
感谢hack,负边带来的问题还挺多的,数据也有点水
哈哈好的,我再学习一下代码,感谢大佬解惑
我就被第二次不能用dfs给坑了, 感谢大佬