树链剖分

什么是树链剖分

将一颗树划分成几条不相交的链,让每一节点都存在且仅存在于一条链中,这样我们就得到了数个线性的结构,然后可以用线段树等数据结构去维护这个整体。
其实就是一个将数据结构推广至树上的方法。
可以用来求解比如查询节点 x 到节点 y 的路径的所有点的 maxsum 等等

该怎么分链

一般我们可以使用轻重边剖分。

重边

每个节点最多只有一条重边连接他与他的子节点。
假如以节点 u 的子节点 v 为根的子树的 SIZE 最大,那么我们就取边 (u,v) 为节点u的重边。

其他边为轻边

相关性质

从根到某一点的路径上轻边的个数都不大于 log2(n)

这个很好想,首先我们对于一颗二叉树的根节点 r 与假设节点 k ,显然有 k 所在的以 a 为根的子树 size(a) 越大,可能的轻边就越多。但要让 (r,a) 这条边为轻边,那 size(a) 就必须小于另外一个子树。所以 size(a) 最大只能取 (size(r)1)/2 。这样递归的做下去,那就是每次/2,就是 log2(n) 了。
对于k叉树,也是相同的,最大取 (size(r)1)/2

重链

顾名思义,重边连成的一条链。

脑洞

其实只要你愿意,完全可以乱分链。 只不过按轻重边划分的话呢,就有了那个很好的轻边性质,保证我们的算法复杂度不会太高。

算法框架

1.找出重边。

2.将重边相连得重链,头尾相连按序放入数据结构。
这里有一个问题,就是叶子节点怎么办,可能不会被重边所连。
那我们就直接当他是链顶,放入线段树。

3.维护求值。

查询与修改

在树上有两个点 u,v ,求u到v路径上的最大点权。
首先我们找到各自链顶 top(u),top(v)
假如 top(u) top(v) 深度更大,那么我们可以更新路径 (u,top(u)) ,因为是一段重链,所以在线段树中应该是相连的,所以直接查询O( log2(n) )。
然后将u跳到father(top(u)),重复上述步骤,直到跳到了同一条重链上为止。即 top(u)=top(v) ,然后再更新路径 (u,v)

修改同理,将查询换为区间修改即可。

时间复杂度

每跳一次重边就相当于跳过了一条轻边。 而且轻边有一个很优美的性质。

从根到某一点的路径上轻边的个数都不大于 log2(n)

我们可以保证最多有 log2(n) 条重链(每条轻边都会断开重链),每一次修改&查询复杂度就是 log2(n)

例题与标程

type
    node=record
        m,s:longint;
    end;
var
    t:array[1..120000] of node;
    sum,tot,u,v,a,b,i,j,k,n,m,q:longint;
    s:char;
    re,cl:node;
    dfp,head,next,father,dep,loc,bl,top,size,w,e,h:array[0..60000] of longint;
function max(a,b:longint):longint;
begin
    if a>b then exit(a) else exit(b);
end;

procedure new(a,b:longint);
begin
    inc(tot);
    e[tot]:=b;
    next[tot]:=head[a];
    head[a]:=tot;
end;
procedure update(x:longint);
begin
    t[x].s:=t[x*2].s+t[x*2+1].s;
    t[x].m:=max(t[x*2].m,t[x*2+1].m);
end;
procedure swap(var a,b:longint);
var t:longint;
begin
    t:=a; a:=b; b:=t;
end;

procedure dfs_h(x,fa,d:longint); {找重边}
var k:longint;
begin
    father[x]:=fa;
    dep[x]:=d;
    inc(size[x]);
    k:=head[x];
    while k<>0 do
    begin
        if e[k]<>fa then
        begin
            dfs_h(e[k],x,d+1);
            inc(size[x],size[e[k]]);
            if size[e[k]]>size[h[x]] then h[x]:=e[k];
        end;
        k:=next[k];
    end;
end;

procedure dfs(x,fa,topp:longint); {连重链一起放入线段树,其他随便放}
var k:longint;
begin
    inc(loc[0]);
    loc[loc[0]]:=x;
    dfp[x]:=loc[0];
    top[x]:=topp;
    if h[x]=0 then exit();
    dfs(h[x],x,topp);
    k:=head[x];
    while k<>0 do
    begin
        if (e[k]<>fa)and(e[k]<>h[x]) then dfs(e[k],x,e[k]);
        k:=next[k];
    end;
end;

procedure build(x,l,r:longint);
begin
    if l=r then
    begin
        t[x].s:=w[loc[l]];
        t[x].m:=w[loc[l]];
        exit();
    end;

    build(x*2,l,(l+r) shr 1);
    build(x*2+1,(l+r) shr 1+1,r);
    update(x);
end;
procedure change(x,l,r,tg,v:longint);
var mid:longint;
begin

    if (l>tg)or(r<tg) then exit();
    if (l=r)and(l=tg) then
    begin
        t[x].s:=v; t[x].m:=v;
        exit();
    end;
    mid:=(l+r) shr 1;
    change(x*2,l,mid,tg,v);
    change(x*2+1,mid+1,r,tg,v);
    update(x);
end;

procedure query(x,l,r,ql,qr:longint);
var mid:longint;
begin
    if (l>qr)or(r<ql) then exit();
    if (ql<=l)and(qr>=r) then
    begin
        inc(re.s,t[x].s);
        if t[x].m>re.m then re.m:=t[x].m;
        exit();
    end;
    mid:=(l+r) shr 1;
    query(x*2,l,mid,ql,qr);
    query(x*2+1,mid+1,r,ql,qr);
end;
procedure find(u,v:longint); {链上查询操作}
var big,small:longint;
begin
    re:=cl;
    while top[u]<>top[v] do
    begin
        if dep[top[u]]<dep[top[v]] then swap(u,v);
        query(1,1,n,dfp[top[u]],dfp[u]);
        u:=father[top[u]];
    end;
    if dfp[u]>dfp[v] then swap(u,v);
    query(1,1,n,dfp[u],dfp[v]);
end;

procedure init;
begin
    readln(n);
    for i:=1 to n-1 do
    begin
        readln(a,b);
        new(a,b);
        new(b,a);
    end;
    for i:=1 to n do
    begin
        read(w[i]);
    end;
    readln(q);
    dfs_h(1,0,1);
    dfs(1,0,1);
    build(1,1,n);
    cl.m:=-maxlongint;
end;
procedure main;
begin
    for i:=1 to q do
    begin
        read(s);
        if s='C' then
        begin
            while s<>' ' do read(s);
            readln(u,v);
            w[u]:=v;
            change(1,1,n,dfp[u],v);
        end else
        begin
            inc(sum);
            read(s,s,s);
            readln(u,v);
            find(u,v);
            if s='X' then writeln(re.m) else writeln(re.s);
        end;
    end;

end;
begin
    init();
    main();
end.

你可能感兴趣的:(树链剖分)