[长链剖分 & 线段树] BZOJ1758[Wc2010] 重建计划

本文深入探讨了长链剖分算法的实现细节,通过具体的代码示例展示了如何利用该算法进行路径查询优化,适用于解决特定类型的问题。

学一发长链剖分
原来以前在ATCODER上做的一题就是这个套路…

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N=100010;

int n,U,D,cnt,dpt[N],d[N],G[N],son[N],fa[N];
struct edge{
  int t,nx;
  double w;
}E[N<<1];

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline void read(int &x){
  char c=nc(); x=0;
  for(;c>'9'||c<'0';c=nc());for(;c>='0'&&c<='9';x=x*10+c-'0',c=nc());
}

inline void addedge(int x,int y,int w){
  E[++cnt].t=y; E[cnt].nx=G[x]; E[cnt].w=w; G[x]=cnt;
  E[++cnt].t=x; E[cnt].nx=G[y]; E[cnt].w=w; G[y]=cnt;
}

void dfs1(int x,int f){
  dpt[x]=dpt[f]+1; fa[x]=f; d[x]=dpt[x];
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=f){
      dfs1(E[i].t,x);
      if(d[E[i].t]>d[son[x]]) son[x]=E[i].t;
      d[x]=max(d[x],d[E[i].t]);
    }
}

int p[N],t;

void dfs2(int x){
  p[x]=++t;
  if(son[x]) dfs2(son[x]);
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=fa[x] && E[i].t!=son[x]) dfs2(E[i].t);
}

double mx[N<<2];

void Build(int g,int l,int r){
  mx[g]=-1e20;
  if(l==r) return ;
  int mid=l+r>>1;
  Build(g<<1,l,mid); Build(g<<1|1,mid+1,r);
}

double cur,f[N];

void pfs(int x){
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=fa[x]){
      f[E[i].t]=f[x]+E[i].w;
      pfs(E[i].t);
    }
}

double Query(int g,int l,int r,int L,int R){
  if(l==L && r==R) return mx[g];
  int mid=L+R>>1;
  if(r<=mid) return Query(g<<1,l,r,L,mid);
  if(l>mid) return Query(g<<1|1,l,r,mid+1,R);
  return max(Query(g<<1,l,mid,L,mid),Query(g<<1|1,mid+1,r,mid+1,R));
}

void Modify(int g,int x,int L,int R,double y){
  mx[g]=max(mx[g],y);
  if(L==R) return ;
  int mid=L+R>>1;
  (x<=mid)?Modify(g<<1,x,L,mid,y):Modify(g<<1|1,x,mid+1,R,y);
}

void dp(int x){
  Modify(1,p[x],1,n,f[x]);
  if(son[x]) dp(son[x]);
  for(int i=G[x],v;i;i=E[i].nx)
    if(E[i].t!=son[x] && E[i].t!=fa[x]){
      dp(v=E[i].t);
      for(int j=1;j<=d[v]-dpt[x] && j<U;j++){
    if(D-j>d[x]-dpt[x]) break;
    cur=max(cur,Query(1,p[v]+j-1,p[v]+j-1,1,n)+Query(1,max(p[x]+D-j,p[x]),min(p[x]+U-j,p[x]+d[x]-dpt[x]),1,n)-2*f[x]);
      }
      for(int j=1;j<=d[E[i].t]-dpt[x];j++)
    Modify(1,p[x]+j,1,n,Query(1,p[v]+j-1,p[v]+j-1,1,n));
    }
  if(D<=d[x]-dpt[x]) cur=max(cur,Query(1,p[x]+D,min(p[x]+U,p[x]+d[x]-dpt[x]),1,n)-f[x]);
}

inline bool check(double x){
  Build(1,1,n); cur=-1e20;
  for(int i=1;i<=cnt;i++) E[i].w-=x;
  pfs(1); dp(1);
  for(int i=1;i<=cnt;i++) E[i].w+=x;
  return cur>-1e-7;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  read(n); read(D); read(U);
  for(int i=1,x,y,w;i<n;i++)
    read(x),read(y),read(w),addedge(x,y,w);
  dfs1(1,0); dfs2(1);
  double L=0,R=1e6,mid;
  int ttt=check(7.5);
  while(R-L>1e-7) check(mid=(L+R)/2)?L=mid:R=mid;
  printf("%.3lf\n",(L+R)/2);
  return 0;
}
&lt;think&gt;嗯,用户之前已经问过处理与深度有关的子树信息,现在他们想知道如何用求子树的k级祖先。我需要先确认用户的问题到底是要找子树中的某个节点的k级祖先,还是直接求某个节点的k级祖先。可能这里存在一点歧义,但通常k级祖先指的是某个节点向上跳k步的祖先,而不是子树内的某个节点。不过用户提到了&ldquo;子树k级祖先&rdquo;,可能需要进一步澄清。 首先,我需要回顾的相关知识。通常用于优化树上与深度相关的查询,比如每个节点的子树中不同深度的节点数。而k级祖先的问题,传统上可以通过倍增法或者树来高效解决,但在这里的应用可能用户不太清楚,所以需要解释清楚。 用户可能已经了解的基础,所以需要直接切入主题,解释如何应用来求解k级祖先。这可能涉及到的预处理步骤,以及如何利用的特性来快速跳跃,从而在O(1)或O(log n)的时间内回答查询。 我需要回忆一下处理k级祖先的具体步骤。通常,将树解为若干条,每个节点所属的度至少为该节点到其子树中最深节点的距离。当处理k级祖先查询时,可以预处理每个的顶点,以及每个顶点向上跳若干步的信息,从而在查询时通过解k的大小,先跳转到某个的顶点,然后再从顶点继续跳转剩余步数。 此外,可能还需要结合倍增法的思想,预先存储每个节点向上跳2^i步的祖先,但的优势在于可以减少预处理的空间或时间。例如,每个的顶点可以存储其所在度,从而在查询时快速确定是否需要跳转到某个顶点,然后再沿着向下查找。 需要注意用户的疑问点可能在于如何将与k级祖先查询结合,以及具体的实现步骤。比如,如何预处理每个节点的信息,如何在查询时解k的步数,以及时间复杂度如何优化到O(1)或较低复杂度。 此外,用户可能混淆了&ldquo;子树中的k级祖先&rdquo;和&ldquo;节点的k级祖先&rdquo;。如果是前者,可能需要重新定义问题,但通常k级祖先是指某个节点的祖先,而不是子树中的某个节点。因此,需要确认问题的正确性,并给出相应的解释。 总结来说,回答需要包括以下几个部: 1. 的基本概念回顾,尤其是与处理深度相关问题的优势。 2. 解释k级祖先问题的定义和应用场景。 3. 详细步骤说明如何使用预处理树结构以支持快速k级祖先查询。 4. 查询时的具体操作步骤,如何解k的步数,利用的信息快速定位祖先。 5. 时间复杂度和空间复杂度的析,对比其他方法(如倍增法)的优势。 6. 可能的示例或伪代码帮助理解。 7. 注意事项和适用场景,指出在此问题中的优势和限制。 需要确保解释清晰,逻辑连贯,避免过于技术化的术语,同时提供足够的细节让用户能够理解实现方法。同时,考虑到用户可能有算法竞赛的背景,需要适当使用相关术语,但保持解释的易懂性。&lt;/think&gt;### **求解子树中某节点的 k 级祖先** --- #### **一、问题定义** **子树中的 k 级祖先**: 对树中的某个节点 \( u \),求其子树中所有节点向上跳 \( k \) 步的祖先节点。 (注:若问题实际为求节点 \( u \) 的 k 级祖先,而非子树中所有节点的祖先,则需调整实现方式。以下假设为前者。) --- #### **二、核心思想** 的核心优势是**高效合并子树深度信息**。针对子树中的每个节点,若需统计其 k 级祖先,可以通过以下步骤实现: 1. **预处理**:为每个节点保存其子树中所有节点的深度信息。 2. **快速查询**:利用的内存复用机制,直接访问预处理结果。 --- #### **三、具体步骤** ##### **1. 预处理子树深度信息** - **数据结构**: 每个节点 \( u \) 维护一个数组 `depth_info[]`,其中 `depth_info[d]` 表示 \( u \) 的子树中深度为 \( d \) 的所有节点的集合。 - **合并方式**: - 继承儿子的 `depth_info[]` 数组(通过指针偏移,无需复制)。 - 合并轻儿子的 `depth_info[]`,逐层向上累加。 ##### **2. 构建祖先关系表** - 对每个节点 \( u \),预处理其向上跳 \( 2^i \) 步的祖先(倍增法),或直接记录每个深度的祖先。 ##### **3. 查询子树中节点的 k 级祖先** - **输入**:子树根节点 \( u \)、目标深度差 \( k \)。 - **操作**: 1. 从 \( u \) 的 `depth_info[]` 中获取所有子树节点的深度 \( d \)。 2. 对每个深度 \( d \) 的节点 \( v \),其 k 级祖先为深度 \( d - k \) 的节点。 3. 通过祖先关系表直接查询 \( v \) 的 \( k \) 级祖先。 --- #### **四、优化实现** ##### **1. 内存复用与指针偏移** - **继承儿子的深度数组**: 父节点直接复用儿子的 `depth_info[]`,通过指针偏移表示深度差。 例如:父节点 \( u \) 的 `depth_info[d]` 对应儿子 \( v \) 的 `depth_info[d-1]`。 - **合并轻儿子数据**: 轻儿子的 `depth_info[]` 逐层合并到父节点的数组中,时间复杂度为轻度的总和(O(n))。 ##### **2. 伪代码示例** ```python class Node: def __init__(self): self.depth_info = [] # depth_info[d]: 子树中深度为d的节点列表 self.long_child = None # 儿子 self.parent = None def dfs(u, parent): u.parent = parent max_depth = -1 # 确定儿子 for v in u.children: dfs(v, u) if len(v.depth_info) &gt; max_depth: max_depth = len(v.depth_info) u.long_child = v # 继承儿子的深度信息 if u.long_child: u.depth_info = u.long_child.depth_info # 通过指针偏移表示当前节点的深度 u.depth_info.insert(0, [u]) # 当前节点的深度为0 else: u.depth_info = [[u]] # 叶子节点 # 合并轻儿子的深度信息 for v in u.children: if v != u.long_child: for d in range(len(v.depth_info)): if d+1 &gt;= len(u.depth_info): u.depth_info.append([]) u.depth_info[d+1].extend(v.depth_info[d]) def query_subtree_k_ancestor(u, k): ancestors = set() for d in range(len(u.depth_info)): for node in u.depth_info[d]: if d &gt;= k: # 找到 node 的 k 级祖先 ancestor = node for _ in range(k): ancestor = ancestor.parent ancestors.add(ancestor) return ancestors ``` --- #### **五、时间复杂度析** - **预处理**:O(n),每个节点被合并的次数等于其所在轻度,总和为 O(n)。 - **查询**:O(m),其中 m 为子树节点数(需遍历所有深度信息)。 --- #### **六、适用场景与限制** - **适用场景**: 需要批量查询子树中所有节点的 k 级祖先(如统计子树中满足某种条件的祖先节点布)。 - **限制**: - 单次查询时间复杂度较高(O(m)),适用于离线预处理后多次查询。 - 若仅需单个节点的 k 级祖先,应改用倍增法(O(log n) 查询)。 --- #### **七、对比其他方法** | 方法 | 预处理时间 | 单次查询时间 | 适用场景 | |--------------|------------|--------------|------------------------------| | | O(n) | O(m) | 子树批量查询 | | 倍增法 | O(n log n) | O(log n) | 单节点实时查询 | | 树 | O(n) | O(log n) | 路径或单节点查询 | --- #### **八、总结** 通过复用深度信息的内存空间,能够高效处理子树中所有节点的深度相关操作(如统计 k 级祖先)。虽然单次查询时间复杂度较高,但其线性预处理和内存优化特性使其在批量查询场景中具有优势。若问题可离线处理,可结合其他算法(如按深度块)进一步优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值