1, 树上倍增
主要用途是维护树上边权/点权最值,但是不支持更新操作 (也可以改成只有更新无查询的)
复杂度是$O(nlogn)-O(logn)$的
若询问边权, 直接跟着$fa$数组上跳即可, 若询问点权需要再多处理没处理到的点
下以维护点权为例, 并且假设点权更新不考虑顺序 (如果考虑顺序则要维护反链)
维护信息用结构体$val$存储, $upd(int\;x)$方法表示用$x$来更新, 预处理了$Log$优化常数
void dfs(int x, int f) { dep[x] = dep[f]+1, fa[x][0] = f; val[x][0].upd(a[x]); REP(i,Log[dep[f]]+1) { fa[x][i]=fa[fa[x][i-1]][i-1]; val[x][i].upd(val[x][i-1]); val[x][i].upd(val[fa[x][i-1]][i-1]); } for (int y:g[x]) if (y!=f) dfs(y,x); } void init() { Log[0]=-1; REP(i,1,n) Log[i]=Log[i>>1]+1; dfs(1,0); } _ query(int x, int y) { _ ret; if (dep[x]<dep[y]) swap(x,y); while (dep[x]>dep[y]) { ret.upd(val[x][Log[dep[x]-dep[y]]]); x = fa[x][Log[dep[x]-dep[y]]]; } if (x==y) { ret.upd(mi[x][0]); return ret; } for (int i=Log[dep[x]]; ~i; --i) if (fa[x][i]!=fa[y][i]) { ret.upd(val[x][i]),ret.upd(val[y][i]); x=fa[x][i],y=fa[y][i]; } ret.upd(val[x][0]); ret.upd(val[x][1]); return ret; }
2, 树链剖分
树剖相当于把普通线段树整到树上了, 可以完成$dfs$序和树上倍增的所有操作, 但复杂度多一个$log$
复杂度$O(n)-O(log^2n)$
(当然如果只查询$lca$不维护其他信息的话复杂度是$O(n)-O(logn)$,要比倍增快)
这里采用线段树维护相关信息, 维护点权很方便, 若维护边权需要把边挂在较深的点上, 转成维护点权, 但要注意边界情况, 下以维护点权为例
用$L$数组记录了每个结点线段树中的编号, $\_update$和$\_query$为线段树相关操作
初始化操作$dfs1(1,0), dfs2(1,1)$
void dfs1(int x, int f, int d) { fa[x]=f, dep[x]=d, sz[x]=1; int mx = -1; for (int y:g[x]) if (y!=f) { dfs1(y,x,d+1); sz[x]+=sz[y]; if (mx<sz[y]) mx=sz[y],son[x]=y; } } void dfs2(int x, int tf) { L[x]=++*L, no[*L]=x, top[x]=tf; if (son[x]) dfs2(son[x],tf); for (int y:g[x]) if (y!=fa[x]&&y!=son[x]) dfs2(y,y); } void update(int x, int y) { while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); _update(1,1,n,L[top[x]],L[x]); x = fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); _update(1,1,n,L[x],L[y]); } void query(int x, int y) { while (top[x]!=top[y]) { if (dep[top[x]]<dep[top[y]]) swap(x,y); _query(1,1,n,L[top[x]],L[x]); x = fa[top[x]]; } if (dep[x]>dep[y]) swap(x,y); _query(1,1,n,L[x],L[y]); }
3, 树的直径
直径长度可以用树形dp很容易求出
void dfs(int x, int fa) { for (int y:g[x]) if (y!=fa) { dfs(y,x); mx = max(mx, d[x]+d[y]+1); d[x] = max(d[x], d[y]+1); } }
要求出直径上所有点的话, 可以两次$dfs$或两次$bfs$, 这里采用两次$bfs$. init()调用之后, vis标记直径上的点, 并存进s里,并用no将直径上点编号
int bfs(int x) {
memset(vis, 0, sizeof vis);
queue<int> q;
fa[x] = 0, vis[x] = 1, q.push(x);
while (!q.empty()) {
x = q.front();q.pop();
for (int y:g[x]) if (!vis[y]) {
vis[y] = 1, fa[y]=x, q.push(y);
}
}
return x;
}
void init() {
r1 = bfs(1), r2 = bfs(r1);
memset(vis,0,sizeof vis);
for (int i=r2; i; i=fa[i]) {
vis[i]=1, s[++*s]=i, no[i]=*s;
}
}
4, 树的重心
树形$dp$即可求出, 需要初始化$maxp[rt=0]=INF$
void getrt(int x, int fa) { sz[x] = 1, maxp[x] = 0; for (int y:g[x]) if (y!=fa) { getrt(y,x), sz[x] += sz[y]; maxp[x] = max(maxp[x], sz[y]); } maxp[x] = max(maxp[x], n-sz[x]); if (maxp[x]<maxp[rt]) rt=x; }
5, 点分治
主要用于处理无根树中所有路径, 直接暴力$dp$可以$O(n^2)$实现, 通过点分治可以达到$O(nlogn)$
$calc$函数用于处理当前子树相关答案, 每次找重心以重心为根分治处理, 由于重心性质, 每次子树大小至少减半, 递归次数$O(logn)$, 总复杂度$O(nlogn)$
使用前初始化$sum=maxv[rt=0]=n, getrt(1,0), solve(rt);$
void getrt(int x, int fa) { maxv[x]=0, sz[x]=1; for (_ e:g[x]) if (!vis[e.to]&&e.to!=fa) { getrt(e.to,x),sz[x]+=sz[y]; maxv[x]=max(maxv[x],sz[y]); } maxv[x]=max(maxv[x],sum-sz[x]); if (maxv[rt]>maxv[x]) rt=x; } void solve(int x) { vis[x]=1,calc(x); for (_ e:g[x]) if (!vis[e.to]) { maxv[rt=0]=n, sum=sz[e.to]; getrt(e.to,0), solve(rt); } }
6, dsu on tree
树上启发式合并, 主要处理静态子树询问, 在转移时保留重儿子答案, 从而保证复杂度是$O(nlogn)$的, 类似于线段树合并, 但空间占用小
需要$dfs1$先预处理出$dfs$序及重儿子, $add$函数对$x$结点更新, $clr$清除更新
void dfs1(int x, int f) { L[x]=++*L,b[*L]=c[x],fa[x]=f,sz[x]=1; int mx = -1; for (int y:g[x]) if (y!=f) { dfs1(y,x); sz[y]+=sz[x]; if (sz[y]>mx) mx=sz[y],son[x]=y; } R[x]=*L; } void dfs(int x, int tp) { for (int y:g[x]) if (y!=fa[x]&&y!=son[x]) dfs(y,0); if (son[x]) dfs(son[x],1); for (int y:g[x]) if (y!=fa[x]&&y!=son[x]) { REP(z,L[y],R[y]) add(b[z]); } add(c[x]), ans[x] = cnt; if (!tp) REP(y,L[x],R[x]) clr(b[y]); }
7, 基环树求环
这里用tarjan求环, 调用后
void dfs(int x) {
dfn[x] = ++*dfn;
for (_ e:g[x]) {
int y = e.to;
if (dfn[y]) {
if (dfn[y]<dfn[x]) continue;
fa[x] = {y,e.w};
a[++*a]=x, vis[x]=1;
for (; y!=x; y=fa[y].to) {
a[++*a]=y, vis[y]=1;
}
}
else fa[y]={x,e.w}, dfs(y);
}
}
待补:动态树, 圆方树, 虚树, 长链剖分, 边分治, 动态点分治, 树上莫队