[日常训练] 联络网

【问题描述】

由于女交警太多,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 向下延伸到叶节点的最长链和次长链的长度,可由x的子节点 y 转移过来,一遍DFS回溯即可求出。
那么对于第二种情况就可表示为 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 时:y向上延伸的最长链长度可由 x 向上延伸的最长链长度与f[x][1]的最大值加一得到,但若 y x向下延伸的最长链上,则不能选取 f[x][1] ,而要选取 f[x][0]

【代码】
#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值