柿子饼同学 发表于 2024-6-22 17:09:22

[心之碎片][0622] - 树网的核

本帖最后由 柿子饼同学 于 2024-6-22 19:30 编辑

树网的核 link
1 遍过
4 h

{:10_279:}
知识点
单调队列


思路
直径的性质: 到一个点的最远距离的点在直径上, 偏心距只会在直径两个端点和不在直径上的路径上产生答案
###1
先找出直径, 记下来, 对于每个直径上的点求出这个点不过直径所能走到最远的点的距离, 记作 val
然后在直径上跑单调队列, 对于一条路径 , 它的偏心距就是 max(路径中val的最大值, 直径两端点到路径端点的最大值)
###2
可以简单一些, 只求出 max(一个点不过直径的最远距离)
根据直径的性质可知 , 当路径不包含这个最大值的点时, 路径端点到直径端点的答案更大
当包含时, 可以让路径端点到直径端点和这个取 max 即可
O(n)


需要进步的地方
代码太长, 加了一些不必要的数组, 要精简代码
一开始写的时候没想清楚, 浪费了时间
整体想不出来, 可以想想单个点是怎么算答案的, 对思考有帮助
先想想一个操作影响的范围, 不要一直想着 memset 0
II 方法很好, 求直径之后沿着 f 数组跳上去即可, 不用截下来或打标记
写 II 的时候 dfs 忘记递归了...{:10_249:}


最终代码

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 5;
typedef long long ll;

struct Edge{
    int v, w;
};

int n;
ll s;
vector<Edge> e;

// f 父节点, d 到根的距离
int p, q, f;
ll d;

void dfs1(int u, int pa, int & id){
    for(auto ed : e){
      if(ed.v == pa) continue;
      f = u;
      d = d + ed.w;

      if(d > d){
            d = d;
            id = ed.v;
      }

      dfs1(ed.v, u, id);
    }
}

void Work(){
    f = 0, d = 0;
    dfs1(1, 0, p); // 直径一端点是 p
    f = 0, d = 0, d = 0;
    dfs1(p, 0, q); // 直径顺序: p -> q
    // 不需要 memset, 后续计算不会算到直径
}

// 求不过直径的最大距离
ll mxd = 0;

void dfs2(int u, int f){
    for(auto ed : e){
      if(ed.v == f) continue;
      d = d + ed.w;
      if(d > mxd) mxd = d;
      dfs2(ed.v, u);
    }
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> s;
    for(int i = 1; i < n; i++){
      int x, y, z;
      cin >> x >> y >> z;
      e.push_back({y, z});
      e.push_back({x, z});
    }

    Work();
   
    // 求 mxd
    // j 是 i 的上一个节点
    for(int i = q, j = 0; i; j = i, i = f){
      for(auto ed : e){
            if(ed.v == f || ed.v == j) continue;

            // 现在只有不在直径上的点了
            d = ed.w;
            mxd = max(mxd, d);
            dfs2(ed.v, i);
      }
    }

    // 尺取路径
    ll ans = 2e18;
    for(int i = q, j = q; i; i = f){
      while(f != f && d - d > s) j = f;
      ans = min(ans, max({mxd, d, d - d}));
    }

    cout << ans;

    return 0;
}一开始的代码
/*
找直径, 标记, 取下直径
每个点求一个不在直径上的侧边最远点距离
每个点的权值 max(最远点距, 直径两端点到点的距离)
单调队列跑直径, 求最大值最小
*/

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 5e5 + 5;

struct Edge{
    int v, w;
};

vector<Edge> e;
int n;
ll m;

// 找最远点
bitset<N> vis;
int f;
ll maxd, id;
ll dist;
int d, cnt;

void dfs(int u, int pa, ll dis){
    f = pa;
    if(dis > maxd){
      maxd = dis;
      id = u;
    }

    for(auto ed : e){
      if(ed.v == pa || vis) continue;
      dfs(ed.v, u, dis + ed.w);
    }
}

// 求直径, 扒下来
void Getd(){
    dfs(1, 0, 0);
    memset(f, 0, sizeof(f));
    maxd = 0;
    int rt = id;
    dfs(rt, 0, 0);
    maxd = 0;
   
    while(id != rt){
      d[++cnt] = id;
      vis = 1;

      for(auto ed : e){
            if(ed.v == f){
                dist = ed.w;
                break;
            }
      }
      id = f;
    }
    vis = 1;
    d[++cnt] = rt;
    for(int i = cnt - 1; i >= 1; i--) dist += dist;
}

// 求权值
ll val;

void Getval(){
    for(int i = 1; i <= cnt; i++){
      dfs(d, f], 0);
      val = maxd;
      maxd = 0;
    }
}

int q, l = 1, r = 0;

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    for(int i = 1; i < n; i++){
      int x, y, z;
      cin >> x >> y >> z;
      e.push_back({y, z});
      e.push_back({x, z});
    }

    Getd();
    Getval();

    int last = 1;
    ll ans = LONG_LONG_MAX;

    for(int i = 1; i <= cnt; i++){
      while(r >= l && val] <= val) r--;
      while(last <= i && dist - dist > m) last++;
      q[++r] = i;
      while(l <= r && q < last) l++;

      ans = min(ans, max({dist - dist, dist, val]}));
    }

    cout << ans;

    return 0;
}

FishC 发表于 2024-6-22 17:10:41

嘿!看起来你在处理一个关于树结构(特别是树的直径和核)的编程问题。让我帮你解读一下这个有点复杂的思路,顺便加点幽默感来让它更容易理解。

### 问题背景:
我们有一个树形结构,树的节点数为 `n`。目标是找出树的“核”,即使得某种距离最小化的特殊点。

### 核心概念解释:
1. **树的直径**:这是树中最长路径的长度。比如说你有一棵大树,从根到最远的叶子走了一圈,那这根最长的树干就是直径。
2. **单调队列**:一种数据结构,用来快速找到某个范围内的最大值或者最小值。就像是去超市购物,你是排在队伍里等着付款,单调队列就有点像你在观察谁最快结账,然后你立刻换队伍。

### 解决思路:
1. **先找到树的直径**。
   - 随意选一个起点,跑一次深度优先搜索(DFS),找出最远的节点(叫它 `p`)。
   - 然后以 `p` 为起点,再跑一次DFS,找到另一个最远的节点(叫它 `q`)。这样我们就有了树的直径。

2. **计算不过直径的最大距离**:
   - 对于直径上的每个节点,找出从它出发到不在直径上的节点的最远距离。这个距离记作 `val`。

3. **使用单调队列优化**:
   - 在直径上用单调队列找出每段路径的偏心距(即路径中所有节点的最大 `val` 值)。

### 代码讲解:
下面是你给出的代码的简化注释版本:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 5e5+5;
typedef long long ll;

struct Edge {
    int v, w;
};

int n;
ll s;
vector<Edge> e;
int p, q, f;
ll d;

void dfs1(int u, int pa, int &id, ll dis) {
    if (dis > d) {
      id = u;
      d = dis;
    }
    for (auto &ed : e) {
      if (ed.v == pa) continue;
      dfs1(ed.v, u, id, dis + ed.w);
    }
}

void findDiameter() {
    memset(d, 0, sizeof(d));
    p = 1; dfs1(p, 0, p, 0); // 找到直径的一端 p
    memset(d, 0, sizeof(d));
    dfs1(p, 0, q, 0); // 找到直径的另一端 q
}

ll max_dist_not_in_diameter;

void dfs2(int u, int pa, ll dist) {
    max_dist_not_in_diameter = max(max_dist_not_in_diameter, dist);
    for (auto &ed : e) {
      if (ed.v == pa) continue;
      dfs2(ed.v, u, dist + ed.w);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> s;
    for (int i = 1; i < n; i++) {
      int x, y, z;
      cin >> x >> y >> z;
      e.push_back({y, z});
      e.push_back({x, z});
    }

    // Step 1: Find the diameter
    findDiameter();
   
    // Step 2: Calculate max distance not in diameter
    max_dist_not_in_diameter = 0;
    for (int i = 1; i <= n; ++i) {
      if (d == 0) { // means node i is not on the diameter
            dfs2(i, 0, 0);
      }
    }

    // Step 3: Traverse diameter and use sliding window (or monotone deque) to find minimum value
    ll ans = LLONG_MAX;
    for (int i = q, j = q; i; i = f) {
      while (f != f && d - d > s) j = f;
      ans = min(ans, max({max_dist_not_in_diameter, d, d - d}));
    }

    cout << ans << endl;
    return 0;
}

### 笑话时间:
想象一下,树的直径就像你每天上学的最长那条公交路线,保证无论多早起床,这条路总是最长的。而树的核呢,就像是你在公交车站等车的时候,发现哪里人少,能最快上车的地方。希望这种比喻能让你的编程之路不再那么枯燥,加油!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

ZZxxccvvbbnnmm 发表于 2024-6-26 20:36:57

MMnnbbvvccxxzz 发表于 2024-6-26 20:37:10

LLkkjjhhggffdds 发表于 2024-6-26 20:37:22

QQQAAAXXX 发表于 2024-6-26 20:37:34

Mnklpo 发表于 2024-6-26 20:37:45

PPooiiuuyyttrre 发表于 2024-6-26 20:38:15

Poklmn 发表于 2024-6-26 20:38:25

AAssddffgghhjjk 发表于 2024-6-26 20:38:40

Qwsazx 发表于 2024-6-26 20:38:58

某一个“天” 发表于 2024-6-28 21:59:51

{:10_256:}

kiara_03 发表于 2024-7-1 14:20:16

harryhan123 发表于 2024-7-2 18:15:32

6

hhy2024 发表于 2024-7-3 19:08:14

听懂掌声

简柠啦 发表于 2024-7-6 23:11:00

很不错{:10_245:}

thekfjie 发表于 2024-7-9 11:30:52

心之碎片太好了

希儿的 发表于 2024-7-9 14:42:16

我目前不懂,但
不影响我大为震撼

harryhan123 发表于 2024-7-12 18:26:30

{:10_254:}

harryhan123 发表于 2024-7-12 18:27:13

{:10_285:}
页: [1] 2
查看完整版本: [心之碎片][0622] - 树网的核