树剖の总结

树剖总结

树剖维护的是树上的链的信息,支持 O ( log ⁡ n ) O(\log n) O(logn) 的区间修改,区间查询。相当于把一棵树压缩成若干链,然后达到快速跳跃的目的(常数比倍增小很多)。

维护以下信息:

f a u fa_u fau u u u 的父亲。

s z u sz_u szu u u u 子树的大小。

d u d_u du u u u 的深度。

s o n u son_u sonu u u u 的重儿子

(一个点的重儿子,为其儿子中子树大小最大的,如有多个则取第一个)

t o p u top_u topu u u u 所在链的链头。

d f n C n t dfnCnt dfnCnt:时间戳,

d f n u dfn_u dfnu:DFS 过程中访问到 u u u 的时间。

r n k x rnk_x rnkx:可以理解为 d f n dfn dfn 的反数组:即若 d f n u = x dfn_u=x dfnu=x,则 r n k x = u rnk_x=u rnkx=u。(这个可以不维护的)

方法:两次 DFS

DFS 1:维护 s z , f a , d , s o n sz,fa,d,son sz,fa,d,son

DFS 2:重点来了(敲黑板)。这里首先是这样的:

  • 我们遍历每条重链,然后标记他的链头 t p tp tp
  • 对于每个 u u u,标记链头 t o p u = t p top_u=tp topu=tp
  • 更新 d f n , r n k dfn,rnk dfn,rnk
  • 若该点没有重儿子,退出 DFS
  • 否则,先走重儿子,链头还是 t p tp tp
  • 之后,遍历 { v ∣ u → v ∈ E } \{v|u\to v\in E\} {vuvE} v ≠ f a u , v ≠ s o n u v\ne fa_u,v\ne son_u v=fau,v=sonu
  • 那么这些链的链头,就是 v v v 了。

好的,有了以上的流程,所有信息都预处理出来了。之后就可以乱搞了(bushi

求 LCA:如果 u u u 所在链头不同于 v v v 所在链头,我们重复以下流程:

  • 把链头深度深的,我们将他变成链头的父亲。

好,那么之后 t o p u = t o p v top_u=top_v topu=topv 了,我们发现 u , v u,v u,v 中,深度较浅的,就是 LCA。

那么还有两点需要处理,就是,

  1. 我如果想要遍历 u → v u\to v uv 路径上所有点,并且更新信息,该怎么做。

我们有一个观察:就是 t o p u → u top_u\to u topuu d f n dfn dfn 时间是连续的。基于此我们有以下流程:

  • 如果 t o p u ≠ t o p v top_u\ne top_v topu=topv,我们重复以下流程:
  • 首先指定 u u u 为链头深度大的, v v v 为另一个。
  • 那么 d f n t o p u ∼ d f n u dfn_{top_u}\sim dfn_u dfntopudfnu 就是要处理的区间。

我们现在有 t o p u = t o p v top_u=top_v topu=topv 了,我们发现,深度浅的到深度深的,就是我们最后一次要处理的区间。

  1. 如果想修改一颗子树内的所有点的信息,该怎么做。

我们有一个观察: u u u 子树内的点构成的 d f n dfn dfn 序列,正好覆盖 [ d f n u , d f n u + s z u − 1 ] [dfn_u,dfn_u+sz_u-1] [dfnu,dfnu+szu1] 这里面的所有时间。那么一切就都方便了。

来一道例题 P3384

板子题不解释。

软件包管理器:P2146

可以发现,依赖关系形成一棵树。对于 a a a 依赖 b b b,我们连一条从 b b b a a a 的有向边。为了写代码方便,我们默认序号从 1 1 1 开始编号。因为我们树是定根的,所以可以。我们发现安装一个软件事实上就是改变 1 ∼ x 1\sim x 1x 的链上的信息。这题,我们记录所有和就行,然后算一下差值。卸载就是把一颗子树清空。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,q;
vector<int> G[N];
int dfn[N],sz[N],dfnCnt,son[N],fa[N],dep[N],top[N];
void dfs1(int u){
  sz[u]=1; //dfn[u]=++dfnCnt;
  for(int v:G[u]) if(v^fa[u]){
    fa[v]=u;
	dep[v]=dep[u]+1;
	dfs1(v);
	sz[u]+=sz[v];
    if(sz[son[u]]<sz[v]) son[u]=v;
  }
}
void dfs2(int u,int tp){
  top[u]=tp; 
  dfn[u]=++dfnCnt;
  if(!son[u]) return;
  dfs2(son[u],tp);
  for(int v:G[u]) 
  if(v!=son[u]&&v!=fa[u])
    dfs2(v,v);
}
int lzy[N<<2],sum[N<<2];
void pushdown(int h,int len){
  if(!~lzy[h]) return; //lzy[h]==-1
  lzy[h<<1]=lzy[h];
  lzy[h<<1|1]=lzy[h];
  sum[h<<1]=(len-len/2)*lzy[h];
  sum[h<<1|1]=(len/2)*lzy[h];
  lzy[h]=-1;
}
void pushup(int h){
  sum[h]=sum[h<<1]+sum[h<<1|1];
}
#define mid (l+r>>1)
void update(int h,int l,int r,int x,int y,int z){
  if(x<=l&&r<=y) 
    return sum[h]=z*(r-l+1),void(lzy[h]=z);
  pushdown(h,r-l+1);
  if(x<=mid) update(h*2,l,mid,x,y,z);
  if(y>mid) update(h*2+1,mid+1,r,x,y,z);
  pushup(h);
}
void updRange(int u,int v,int w){
  while(top[u]!=top[v]) {
    if(dep[top[u]]<dep[top[v]]) swap(u,v);
    update(1,1,n,dfn[top[u]],dfn[u],w);
    u=fa[top[u]];
  }
  if(dep[u]>dep[v]) swap(u,v);
  update(1,1,n,dfn[u],dfn[v],w);
}

void Out(const int now,int l,int r){
	if(l==r){
		cout<<sum[now]<<' ';
		if(r==n) cout<<endl;
		return ;
	}
	pushdown(now,r-l+1);
	Out(now<<1,l,mid);Out(now<<1|1,mid+1,r);
}

main(){
  cin.tie(0)->sync_with_stdio(0);
  cin>>n;
  memset(lzy,-1,sizeof lzy);
  for(int i=1,u;i<n;++i){
    cin>>u; ++u;
    if(u) G[u].push_back(i+1);
  }
  dfs1(1);dfs2(1,1);
  cin>>q;
  while(q--){
    int x; char op[20];
    cin>>op>>x;
    ++x;
    int pre=sum[1];
    if(*op=='i') updRange(1,x,1);
    else if(*op=='u') update(1,1,n,dfn[x],dfn[x]+sz[x]-1,0);
    cout<<abs(pre-sum[1])<<'\n';
//    Out(1,1,n);
  }
}

P4374

我们发现每条额外的边 u → v u\to v uv,都会整合所有 u u u v v v 路径上的边。也就是说,如果我断掉任意一条路径上的边,我都可以拿这条额外的边替代。于是我们就有了一个区间取 min ⁡ \min min 的做法。用 d f n u dfn_u dfnu 代指 f a u → u fa_u\to u fauu 的边。于是我们修改 u u u v v v 路径上的边,和上面的不同就在于最后只需要深的点,到浅的点的 s o n son son 即可。这样可以发现,也是不重不漏的。那么询问就是单点询问了,可以线段树 O ( log ⁡ n ) O(\log n) O(logn) 做。询问的,事实上就是深的点的 d f n dfn dfn

#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,m,fa[N],sz[N],d[N],son[N],top[N],dfnCnt,dfn[N];
vector<int> G[N];
void dfs1(int u){
  sz[u]=1; for(int v:G[u]) if(v^fa[u])
    fa[v]=u,d[v]=d[u]+1,dfs1(v),sz[u]+=sz[v],sz[son[u]]<sz[v]&&(son[u]=v);
}
void dfs2(int u,int tp){
  top[u]=tp,dfn[u]=++dfnCnt;
  if(!son[u]) return;
  dfs2(son[u],tp);
  for(int v:G[u]) if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
int Mn[N<<2],Lzy[N<<2];
constexpr int Inf=0x3f3f3f3f;
#define Tree int h,int l,int r
#define ls h*2
#define rs ls|1
#define mid (l+r>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
void pushup(int h){
  Mn[h]=min(Mn[ls],Mn[rs]);
}
void smin(int &x,int y){x=min(x,y);}
void pushdown(int h){
  if(Lzy[h]==Inf) return;
  smin(Mn[ls],Lzy[h]);
  smin(Mn[rs],Lzy[h]);
  smin(Lzy[ls],Lzy[h]);
  smin(Lzy[rs],Lzy[h]);
  Lzy[h]=Inf;
}
void updRange(Tree,int x,int y,int w){
  if(x<=l&&r<=y) return smin(Mn[h],w),smin(Lzy[h],w);
  pushdown(h);
  if(x<=mid) updRange(lson,x,y,w);if(y>mid) updRange(rson,x,y,w);
  pushup(h);
}
int ask(Tree,int x){
  if(l==r) return Mn[h];
  pushdown(h);
  if(x<=mid) return ask(lson,x);
  else return ask(rson,x);
}
void update(int u,int v,int w){
  for(;top[u]!=top[v];u=fa[top[u]]){
    if(d[top[u]]<d[top[v]]) swap(u,v);
    updRange(1,1,n,dfn[top[u]],dfn[u],w);
  }
  if(d[u]>d[v]) swap(u,v);
  if(u==v) return;
  updRange(1,1,n,dfn[/*son[u]*/u]+1,dfn[v],w);
}
int id[N],E[N];
pair<int,int> e[N];
void get(Tree){
  if(l==r) return void(id[l]=h);
  pushdown(h);
  get(lson),get(rson);
}
main(){
  cin.tie(0)->sync_with_stdio(0);
  cin>>n>>m;
  memset(Mn,0x3f,4*n+10<<2);
  memset(Lzy,0x3f,4*n+10<<2);
  for(int i=1;i<n;++i){
    int u,v; cin>>u>>v;
    G[u].push_back(v);
    G[v].push_back(u);
    e[i]={u,v};
  }
  dfs1(1),dfs2(1,1);
  for(int i=1;i<n;++i){
    auto[u,v]=e[i];
    if(d[u]>d[v]) E[i]=u;
    else E[i]=v;
  }
  for(int i=1;i<=m;++i){
    int u,v,w;cin>>u>>v>>w;
    update(u,v,w);
  }
  get(1,1,n);
  for(int i=1;i<n;++i){
    int&ans=Mn[id[dfn[E[i]]]];
    if(ans==0x3f3f3f3f) cout<<"-1\n";
    else cout<<ans<<'\n';
  }
}
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值