【问题描述】
由于女交警太多,lpq与她们的联络就成了一个问题。他决定构建一个联络网以便联系。
lpq不希望网络太复杂,于是他把联络网设计成了一棵树,这样每次需要联络的时候lpq只需给任意一个女交警发短信便可以将信息传达给所有的女交警。我们可以认为女交警从接到短信到群发出去的时间为1。
在这个联络网用了一段时间后,lpq发现了一个问题:当他给某个女交警发短信时,转达到一些人需要的时间非常长,这样导致耽误了约会。而lpq又不愿意浪费短信费,他希望每次需要联络时只给一个女交警打电话。lpq十分苦恼,他决定修改联络网的一条树边(把这条树边删去,再添加一条边)。他希望使修改后的联络网满足:从给任意一个女交警发短信开始,到联络到所有女交警的最长时间尽量短。
lpq找到了你帮忙,你一定要帮帮他!
【输入格式】
输入文件network.in包含若干行。
第一行包含一个整数N,表示有N个女交警。
接下来的N-1行,每行两个整数A、B,表示A与B之间有边相连。
【输出格式】
输出文件network.out只包含一行,为一个整数,表示修改后联络的最短时间。
【样例输入】
5
1 2
2 3
3 4
4 5
【样例输出】
3
【数据范围】
对于30%的数据,n≤50
对于100%数据,n≤2500
【30分】 O(n4) 树形DP
如果要删除或添加某条边,最暴力的思想是考虑直接
O(n3)
枚举,则我们只要求出这样暴力重构的新树的最长链。
对于包含某一个点的最长链,显然有如下两种情况:
我们记
f[x][1]
和
f[x][0]
分别表示自点
x
向下延伸到叶节点的最长链和次长链的长度,可由
那么对于第二种情况就可表示为
f[x][1]+f[x][0]
,而第一种情况因为我们要遍历到根节点,实际也会考虑进去。
【100分】 O(n2) 树形DP
思考后我们可以发现:实际上我们枚举
O(n)
删除边后,新生成的两棵树的最长链可以由如下图的三种情况得到:
[1]、新生成的第一棵树的最长链(黑);
[2]、新生成的第二棵树的最长链(蓝);
[3]、将第一棵树的某一点向第二棵树的某一点添加一条边,经过这条新边所形成的新的最长链(红)。
前两种情况就是我们上述的最长链DP,第三种情况的两点实际上是可以贪心得到的,我们选取两棵树中向上和向下延伸的最长链长度的最大值最小的两个节点作为答案(这是为什么呢?首先最大值保证包含该节点能构成最长链,最小是题目要求的最优方案)。
同样,向下延伸的最长链长度可以由
f[x][0]+f[x][1]
得到,向上延伸的最长链长度我们可以通过第二遍
DFS
得到。当
DFS
到节点
y
时:
【代码】
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int Maxn = 1 << 30;
const int N = 2505, M = 5005;
int f[N][2], dep[N], lst[N], to[M], nxt[M], fr[M], lft[N], lar[N];
int Ans, n, EdAns = Maxn, tx, ty, T = 1;
inline int get()
{
char ch; int res = 0;
while ((ch = getchar()) < '0' || ch > '9');
res = ch - '0';
while ((ch = getchar()) >= '0' && ch <= '9')
res = (res << 3) + (res << 1) + ch - '0';
return res;
}
inline void addEdge(const int &x, const int &y)
{
nxt[++T] = lst[x]; lst[x] = T; to[T] = y; fr[T] = x;
nxt[++T] = lst[y]; lst[y] = T; to[T] = x; fr[T] = y;
}
inline void CkMax(int &x, const int &y){if (x < y) x = y;}
inline int Max(const int &x, const int &y){return x > y ? x : y;}
inline void CkMin(int &x, const int &y) {if (x > y) x = y;}
inline void Dfs1(const int &x, const int &fa)
{
f[x][0] = f[x][1] = -1;
for (int i = lst[x], y; i; i = nxt[i])
{
if ((y = to[i]) == fa) continue;
Dfs1(y, x); int tmp = f[y][1] + 1;
if (tmp > f[x][1])
f[x][0] = f[x][1], f[x][1] = tmp, lft[x] = y;
else if (tmp > f[x][0]) f[x][0] = tmp;
}
}
inline void Dfs2(const int &x, const int &fa, const int &Cast, int &st)
{
int tmp = Max(Cast, f[x][1]) + 1;
CkMax(Ans, tmp); CkMin(st, tmp);
for (int i = lst[x], y; i; i = nxt[i])
{
if ((y = to[i]) == fa) continue;
if (y == lft[x]) Dfs2(y, x, Max(Cast, f[x][0]) + 1, st);
//lft[x]用来判断y是否在x向下延伸的最长链上
else Dfs2(y, x, tmp, st);
}
}
int main()
{
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
n = get();
for (int i = 1; i < n; ++i) addEdge(get(), get());
for (int i = 2; i <= T; i += 2)
{
tx = ty = Maxn; Ans = 0;
Dfs1(fr[i], to[i]); Dfs2(fr[i], to[i], -1, tx);
Dfs1(to[i], fr[i]); Dfs2(to[i], fr[i], -1, ty);
CkMax(Ans, tx + ty + 1);
if (EdAns > Ans) EdAns = Ans;
}
printf("%d\n", EdAns);
fclose(stdin); fclose(stdout);
return 0;
}