树论板子

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);
    }
}

 

 

 

 

 

待补:动态树, 圆方树, 虚树, 长链剖分, 边分治, 动态点分治, 树上莫队

 

 

转载于:https://www.cnblogs.com/uid001/p/10284618.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值