题目大意
给出一棵树,现在根节点为 1 1 1号节点,每天都有警察从一号节点出发,到每条路上巡逻然后回到根节点。现在可以新建 k ( 1 ≤ k ≤ 2 ) k(1 \leq k \leq 2) k(1≤k≤2)条道路,新建的道路只能经过恰好一次,其余路经过至少一次,求新建道路后巡逻的最短距离。
解题思路
假设不连接道路,从 1 1 1号节点走到所有的节点再回去恰好走了每条边两次。可以手玩一下样例,然后就可以发现这个规律
- 当 k = 1 k=1 k=1时,不难发现,需要形成一个最大的基环树,联系到树的直径,其实就是求出树的直径 l e n len len然后连接起点终点,这样环上的边只走一次,总的次数为 2 ∗ ( n − 1 ) − l e n + 1 2*(n-1)-len+1 2∗(n−1)−len+1,样例同样给出了图示
- 当
k
=
2
k=2
k=2时,可以猜想到是在
1
1
1的基础上再连接一个环形成两个环(这个可以证明,但是由于赶时间就不说了)。那么在第二个环和第一个环显然有以下几种关系
– 第二个环和第一个环有若干个公共边
– 第二个环和第一个环无公共边
– 第二个环是第一个环的一部分
如下图所示,连接 4 , 6 4,6 4,6即为第一种情况;连接 6 , 7 6,7 6,7即为第第二种情况;连接 1 , 8 1,8 1,8为第三种情况
显然在这个样例中第三种情况是一定被舍弃的。考虑第一种情况,即如果和第一个环重复的边有 x x x条,那么这 x x x条是需要走两次的,即答案为 2 ∗ ( n − 1 ) − l e n 1 + 1 − l e n 2 + 1 − x 2*(n-1)-len1+1-len2+1-x 2∗(n−1)−len1+1−len2+1−x,相当于这几条边不在所选择的路径上。这里用到一个非常奇妙的思想,我们设置重复的边的权值为-1,那么计算权值和(路径长度)时相当于直接减去了重复的这部分。因此只需要使用树形DP计算第二条路径,实际上就是新图的直径
注意点:
- 在更新第一个直径的权值显然需要得到路径,那么第一个直径建议两次 d f s dfs dfs求,这样可以在记录下起点和终点然后递归求路径。因为我们存的是边,那么先记录下环上的点,然后对每个点的边集遍历,只有边的起点终点均出现在map中才更新这条边的权值(第一次我写的是记录pair,然后在dp时判断,但是这样TLE了一个点,我想可能是哈希冲突了)
- 含负权的树求直径不能使用两次 d f s dfs dfs,需要使用树形DP
- 因为可以自环,那么第二次求出的答案如果小于0就直接更新为0
//
// Created by Happig on 2020/8/19
//
#include <bits/stdc++.h>
#include <unordered_map>
#include <unordered_set>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define ins insert
#define Vector Point
#define lowbit(x) (x&(-x))
#define mkp(x, y) make_pair(x,y)
#define mem(a, x) memset(a,x,sizeof a);
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<double, double> pdd;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const double dinf = 1e300;
const ll INF = 1e18;
const int Mod = 1e9 + 7;
const int maxn = 4e5 + 10;
struct node {
int next, to, w;
} edge[maxn];
int f[maxn], head[maxn], tot, maxd, s, t;
ll d[maxn], ans;
unordered_map<int, int> mp;
void add_edge(int u, int v, int w) {
tot++;
edge[tot] = {head[u], v, w};
head[u] = tot;
tot++;
edge[tot] = {head[v], u, w};
head[v] = tot;
}
void dfs(int u, int fa, int cur) {
f[u] = fa;
if (maxd < cur) {
maxd = cur;
t = u;
}
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != fa) dfs(v, u, cur + 1);
}
}
void dp(int u, int fa) {
// d[u] = (mp.count({u, fa}) || mp.count({fa, u}) ? -1 : 0);
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to, w = edge[i].w;
if (v == fa) continue;
dp(v, u);
ans = max(ans, d[u] + d[v] + w);
d[u] = max(d[u], d[v] + w);
}
}
int main() {
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
add_edge(u, v, 1);
}
dfs(1, -1, 0);
maxd = 0, s = t;
dfs(t, -1, 0);
//cout<<s<<" "<<t<<endl;
if (k == 1) {
cout << 2 * (n - 1) - maxd + 1 << "\n";
} else {
while (f[t] != -1) {
mp[t] = 1;
//cout<<t<<endl;
t = f[t];
}
mp[t] = 1;
for (int i = 1; i <= n; i++)
if (mp.count(i)) {
for (int j = head[i]; j; j = edge[j].next)
if (mp.count(edge[j].to)) edge[j].w = -1;
}
ans = -inf;
dp(1, -1);
//cout << maxd << " " << ans << endl;
if (ans < 0) ans = 0;
ans += maxd;
cout << 2 * n - ans << "\n";
}
return 0;
}