【洛谷 P3304】[SDOI2013]直径(树的直径)

该博客围绕求一棵树被所有直径经过的边的条数这一问题展开。博主最初采用暴力求所有直径并树上差分统计的方法未成功,之后给出思路:先找一条直径,从端点遍历,找到第一个能分叉出直径的点,再往回找第一个能分叉的点,两点间路径的边即为所求。

题目链接
题意,求一棵树被所有直径经过的边的条数。
这题是我们8.25KS图论的最后一题,当时我果断打了暴力求所有直径然后树上差分统计的方法,好像有点小问题,boom0了。
考完改这题,改了好久,各种各样的小bug,至少有七八个。。。
思路:先随便找一条直径,然后从一个端点开始遍历这条直径,如果当前点能分叉出一条直径,那么这条直径后面的点都不可能被所有直径穿过了,于是我们找到第一个能分叉的点,然后再从这个点往回遍历,找到第一个能分叉的点,这2个点中间的路径的边即为所求。
代码就算了吧,打这题时我就没想过要有可读性

#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 2147483647
#define Open(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define Close fclose(stdin); fclose(stdout);
const int MAXN = 200010;
inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') { s = s * 10 + ch - '0'; ch = getchar(); }
    return s * w;
}
struct Edge{
    int next, to, dis;
}e[MAXN << 1];
int num, head[MAXN], flag[MAXN];
inline void Add(int from, int to, int dis){
    e[++num] = (Edge){ head[from], to, dis };
    head[from] = num;
}
int pre[MAXN], in[MAXN];
int n, ans, Maxu, ans1;
long long Max;
void dfs(int u, int fa, long long dep){
    if(dep > Max) Max = dep, Maxu = u;
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa)
         dfs(e[i].to, u, dep + e[i].dis);
}
void DFS(int u, int fa, long long dep){
    pre[u] = fa;
    if(dep > Max) Max = dep, Maxu = u;
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa)
         DFS(e[i].to, u, dep + e[i].dis);
}
bool existOther(int u, int fa, long long dep){
    if(dep == Max) return true;
    for(int i = head[u]; i; i = e[i].next)
       if(e[i].to != fa)
         if(existOther(e[i].to, u, dep + e[i].dis)) return true;
    return false;
}
int A, B, C;
int main(){
    Open("diameter");
    n = read();
    for(int i = 1; i < n; ++i){
       A = read(); B = read(); C = read();
       Add(A, B, C); Add(B, A, C);
    }
    Max = -1; dfs(1, 0, 0);
    Max = -1; DFS(Maxu, 0, 0);
    int now = Maxu, o = 0, fa = 0, Plus = 0;
    long long deep = 0;
    while(now) flag[now] = 1, now = pre[now];
    for(int i = 1; i <= n; ++i) if(!flag[i]) pre[i] = 0;
    now = Maxu;
    while(now){
      Plus = 0;
      for(int i = head[now]; i; i = e[i].next)
         if(e[i].to != pre[now] && e[i].to != fa){
           if(existOther(e[i].to, now, deep + e[i].dis)){
             o = now;
             break;
           }
         }
         else if(e[i].to == pre[now]) Plus = e[i].dis;
      if(!o && !pre[now]) o = now;
      if(o) break;
      fa = now;
      now = pre[now];
      deep += Plus;
    }                           //找到第一个分叉的点
        ans = 0;       //往回遍历
        now = o;
        fa = pre[now];
        deep = Max - deep;
        while(now != Maxu){
          int nxt;
          ++ans;
          for(int i = head[now]; i; i = e[i].next)
             if(pre[e[i].to] != now && e[i].to != fa){
               if(existOther(e[i].to, now, deep + e[i].dis)){
                 printf("%I64d\n%d\n", Max, ans - 1);
                 //system("pause");
                 return 0;
               }
             }
             else if(pre[e[i].to] == now) nxt = e[i].to, Plus = e[i].dis;
          fa = now;
          now = nxt;
          deep += Plus;
        }
    printf("%I64d\n%d\n", Max, ans);  //如果都不能分叉
    Close;
    //system("pause");
    return 0;
}

转载于:https://www.cnblogs.com/Qihoo360/p/9541297.html

### 洛谷 P4016 直径 题解及算法 直径是指上任意两点之间的最长简单路径。对于洛谷 P4016 直径问题,可以通过两种常见方法求解:**双DFS法** 和 **形DP法**。 #### 双DFS法 双DFS法是一种基于深度优先搜索(DFS)的方法,通过两次遍历来找到直径。首先从任意节点出发,找到距离最远的节点 `u`,然后从节点 `u` 再次进行DFS,找到距离最远的节点 `v`,那么从 `u` 到 `v` 的路径即为直径。 以下是双DFS法的具体实现代码: ```cpp #include <bits/stdc++.h> using namespace std; const int N = 1e5 + 10; vector<int> g[N]; int d[N]; void dfs(int now, int fa, int dep) { d[now] = dep; for (auto &nex : g[now]) { if (nex == fa) continue; dfs(nex, now, dep + 1); } } int main() { int n; cin >> n; for (int i = 1; i <= n - 1; i++) { int u, v; cin >> u >> v; g[u].push_back(v); g[v].push_back(u); } dfs(1, -1, 0); // 第一次DFS,从任意节点开始 int s = 1; for (int i = 1; i <= n; i++) { if (d[i] > d[s]) s = i; } dfs(s, -1, 0); // 第二次DFS,从第一次找到的最远点s开始 int t = 1; for (int i = 1; i <= n; i++) { if (d[i] > d[t]) t = i; } cout << d[t]; // 输出直径长度 } ``` 这种方法的时间复杂度为 \(O(n)\),适用于无权图或边权均为正的情况[^2]。 #### 形DP法 形DP法是一种动态规划方法,可以处理带负边权的情况。其核心思想是通过递归计算每个子的最大深度,并更新全局最大直径值。 以下是形DP法的具体实现代码: ```cpp #include <bits/stdc++.h> using namespace std; const int N = 1e6 + 10; int h[N], e[N], ne[N], w[N], idx; int d[N], maxd, n; void add(int a, int b, int c) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } void dfs(int u, int fa) { for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i], k = w[i]; if (j == fa) continue; dfs(j, u); maxd = max(maxd, d[u] + d[j] + k); // 更新直径 d[u] = max(d[u], d[j] + k); // 更新当前节点的最大深度 } } int main() { cin >> n; memset(h, -1, sizeof h); for (int i = 0; i < n - 1; i++) { int a, b; cin >> a >> b; add(a, b, 1), add(b, a, 1); // 假设边权为1 } dfs(1, -1); cout << maxd; // 输出直径长度 } ``` 形DP法的时间复杂度同样为 \(O(n)\),并且能够适配带有负边权的情况[^1]。 #### 总结 无论是双DFS法还是形DP法,都可以高效地解决直径问题。双DFS法更直观且易于实现,而形DP法则更加灵活,能够处理更复杂的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值