【树链剖分】学习笔记

【前言】

这几天看了看树链剖分,但没来得及做笔记,(什么叫好记性不如烂笔头)
本文于3月30日第三次更新。

树链剖分

  把一棵树分成若干条链,然后利用数据结构(BIT,SBT,Splay,线段树等,通常是线段树)来维护链(log n)。

一些定义:

 size[]:保存以x为根的子树的节点个数
 top[]:保存当前节点所在链的顶端节点
 son[]:保存重儿子
 fa[]:父节点
 dep[]:节点深度
 tid[]:保存节点在剖分后的新编号
 Rank[]:线段树中的这个位置是哪个点(Rank的R要大写!!!)

 重儿子:一个结点size值最大的儿子
 轻儿子:其余的儿子
 重边:与重儿子连接的边
 轻边:与轻儿子连接的边
 重链:由重边所连接成的链

 剖分后的树有一些性质:
  (1)轻边(u,v),size[v] <= size[u] / 2
  (2)由根到某点的路径上,不超过 log n 条轻边和 log n 条重链

算法实现:

 先是对树进行剖分。
 一:找重边,并记录:

void dfs1(int v,int pa,int d) {
    dep[v] = d;
    fa[v] = pa;
    size[v] = 1;
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,d+1);
            size[v] += size[e[i].to];
            if(son[v] != -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

 二:连接重边形成重链
   以根节点为起点,沿重边向下拓展,拉成重链。不在当前重链上的结点,都以该节点为起点向下重新拉一条重链。
   剖分完毕后,每条重链相当于一段区间,然后用数据结构维护。
   当然咯,每条链都建数据结构不清真,所以将所有链首尾相接,放到一个数据结构上,然后维护整体。

void dfs2(int v,int tp)
{
    top[v] = tp;  tid[v] = ++tim;//timÓÃÓÚ±ê¼ÇλÖà 
    Rank[tid[v]] = v;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != son[v] && e[i].to != fa[v])
            dfs2(e[i].to,e[i].to);
}

 然后就是对树的一些操作。
 例如:将u到v的路径上每边权值加上一个数。
   设f1 = top[u] , f2 = top[v]
   若f1 != f2,设dep[f1] >= dep[f2],那么就更新u到f1父亲的权值(log n),再使u = fa[f1];
   当f1 == f2,说明u和v在同一条链上,直接更新。
   重复上述过程即可。
 对于求和,求极值等操作差不多皆如此。

一点模板:

HDU 3966

给棵树,有权值,有操作:
I:从x到y的路径每条加w
D:从x到y的路径每条减w
Q:查询某条路径

【题解】:
树链剖分模板题。详见代码.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 50005

int n,tim,p;
int dep[N],fa[N],top[N],son[N],size[N],tid[N],Rank[N],a[N];
struct node{int to,next;}e[N*2];
int head[N],m;

void init() {
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
    m = 0; tim = 0;
}

void add_edge(int from,int to) {
    e[++m].next = head[from];
    head[from] = m;
    e[m].to = to;
}

void dfs1(int v,int pa,int d)
{
    dep[v] = d; fa[v] = pa; size[v] = 1;
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,d+1);
            size[v] += size[e[i].to];
            if(son[v] == -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

void dfs2(int v,int tp)
{
    top[v] = tp; tid[v] = ++tim;
    Rank[tid[v]] = v;
    if(son[v] == -1) return;
    dfs2(son[v],tp);// look for the fattest son
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != son[v] && e[i].to != fa[v])
            dfs2(e[i].to,e[i].to);
}

#define lson o << 1
#define rson o << 1 | 1
int sum[N << 2],add[N << 2];
void build(int o,int l,int r)
{
    add[o] = 0;
    if(l == r){sum[o] = a[Rank[l]];return;}
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
}

void pushdown(int o,int l,int r)
{
    if(add[o]) {
        int mid = (l+r)>>1;
        add[lson] += add[o]; sum[lson] += add[o]*(mid-l+1);
        add[rson] += add[o]; sum[rson] += add[o]*(r-mid);
        add[o] = 0;
    }
}

void update(int o,int l,int r,int ll,int rr,int w)
{
    if(ll <= l && rr >= r) {
        add[o] += w;
        sum[o] += (r-l+1) * w;
        return;
    }
    pushdown(o,l,r);
    int mid = (l+r)>>1;
    if(ll <= mid) update(lson,l,mid,ll,rr,w);
    if(rr > mid) update(rson,mid+1,r,ll,rr,w);
}

void change(int x,int y,int w)
{
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        update(1,1,n,tid[top[x]],tid[x],w);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    update(1,1,n,tid[x],tid[y],w);
}

int query(int o,int l,int r,int pos)
{
    if(l == r) return sum[o];
    pushdown(o,l,r);
    int mid = (l+r)>>1;
    if(pos <= mid) return query(lson,l,mid,pos);
    else return query(rson,mid+1,r,pos);
}   

int main()
{
    char opt[3];
    int u,v,w;
    while(scanf("%d%d%d",&n,&m,&p) != EOF)
    {
        init();
        for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
        for(int i = 1;i < n;i++) {
            scanf("%d%d",&u,&v);
            add_edge(u,v); add_edge(v,u);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        build(1,1,n);
        while(p--) {
            scanf("%s",opt);
            if(opt[0] == 'Q') {
                scanf("%d",&w);
                printf("%d\n",query(1,1,n,tid[w]));
            }
            else {
                scanf("%d%d%d",&u,&v,&w);
                if(opt[0] == 'D') w = -w;
                change(u,v,w);
            }
        }
    }
    return 0;
}

SPOJ Query on a tree

给定一棵树,告诉了每条边的权值,然后给出两种操作:
(1)把第i条边的权值改为val
(2)询问a,b路径上权值最大的边

【题解】:
也是树链剖分的板子题。
  只是这题保存的是边权,我们用这条边的孩子结点来表示边权,就可以使用树链剖分来做了。
  但是要注意,查询时,两个结点的LCA的点所表示的边权不在u到v的路径上,所以到最后成为一条链时要将上方的点的tid值加1。(我就WA在这)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 50010
const int inf = 1<<30;
struct node{int to,w,next,pos;}e[N*2];
int head[N],m;
int n,tim;
int a[N],size[N],top[N],son[N],dep[N],tid[N],Rank[N],fa[N],whe[N];

void init() {
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
    m = tim = 0;
}

void add_edge(int from,int to,int cost,int pos) {
    e[++m].next = head[from];
    head[from] = m;
    e[m].to = to; e[m].w = cost; e[m].pos = pos;
}

void dfs1(int v,int pa,int d,int cost,int pos)
{
    a[v] = cost; whe[pos] = v;//将边保存到孩子节点上
    dep[v] = d; fa[v] = pa; size[v] = 1;
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,d+1,e[i].w,e[i].pos);
            size[v] += size[e[i].to];
            if(son[v] == -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

void dfs2(int v,int tp)
{
    top[v] = tp; tid[v] = ++tim; Rank[tid[v]] = v;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != fa[v] && e[i].to != son[v])
            dfs2(e[i].to,e[i].to);
}

#define lson o << 1
#define rson o << 1 | 1
int maxn[N << 2];

void pushup(int o) {
    maxn[o] = max(maxn[lson],maxn[rson]);
}

void build(int o,int l,int r)
{
    if(l == r) { maxn[o] = a[Rank[l]]; return; }
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
    pushup(o);
}

void update(int o,int l,int r,int pos,int v)
{
    if(l == r){maxn[o] = v;return;}
    int mid = (l+r)>>1;
    if(pos <= mid) update(lson,l,mid,pos,v); else update(rson,mid+1,r,pos,v);
    pushup(o);
}

int query(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) return maxn[o];
    int mid = (l+r)>>1,ans= -inf;
    if(ll <= mid) ans = max(ans,query(lson,l,mid,ll,rr));
    if(rr > mid) ans = max(ans,query(rson,mid+1,r,ll,rr));
    return ans;
}

int Query(int x,int y)
{
    int ans = -inf;
    while(top[x] != top[y])
    {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans = max(ans,query(1,1,n,tid[top[x]],tid[x]));
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    return max(ans,query(1,1,n,tid[x]+1,tid[y]));//就是这里,要将tid[x]的值加1!!!
}

int main()
{
    char opt[5];
    int t,u,v,w;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d",&n);
        for(int i = 1;i < n;i++) {
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w,i); add_edge(v,u,w,i);
        }
        dfs1(1,0,0,0,0);
        dfs2(1,1);
        build(1,1,n);       
        while(scanf("%s",opt) && opt[0] != 'D') {
            scanf("%d%d",&u,&v);
            if(opt[0] == 'Q') printf("%d\n",Query(u,v));
            else update(1,1,n,tid[whe[u]],v);
        }
    }
    return 0;
}

3月30日再次更新:
BZOJ 1036 【ZJOI 2008】树的统计(count)

给一棵树,每个点有权值:
CHANGE:修改点值
QMAX:求一个u到v路径上点权最大值
QSUM:求一个路径权值和

【题解】:
  这题唯一要注意的是权值可以为负,所以在求最小值时要将初始设为负无穷。

#include<cstdio>
#include<cstring>
#include<algorithm>

#define N 30010
using namespace std;

struct node{int to,next;}e[N * 2];
int head[N],m;
int a[N],dep[N],top[N],son[N],size[N],fa[N],tid[N],Rank[N];
int n,tim;

void init() {
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
    tim = m = 0;
}

void add_edge(int from,int to) {
    e[++m].next = head[from];
    head[from] = m;
    e[m].to = to;
}

void dfs1(int v,int pa,int d)
{
    dep[v] = d; fa[v] = pa; size[v] = 1;
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,d+1);
            size[v] += size[e[i].to];
            if(son[v] == -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

void dfs2(int v,int tp)
{
    top[v] = tp; tid[v] = ++tim;
    Rank[tid[v]] = v;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != son[v] && e[i].to != fa[v])
            dfs2(e[i].to,e[i].to);
}

#define lson o << 1
#define rson o << 1 | 1
#define inf 1000000000
int sum[N << 2],maxn[N << 2];

void pushup(int o) {
    maxn[o] = max(maxn[lson],maxn[rson]);
    sum[o] = sum[lson] + sum[rson];
}

void build(int o,int l,int r)
{
    if(l == r) {sum[o] = maxn[o] = a[Rank[l]]; return;}
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
    pushup(o);
}

void update(int o,int l,int r,int pos,int v)
{
    if(l == r) {sum[o] = maxn[o] = v; return;}
    int mid = (l+r)>>1;
    if(pos <= mid) update(lson,l,mid,pos,v); else update(rson,mid+1,r,pos,v);
    pushup(o);
}

int query_max(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) return maxn[o];
    int mid = (l+r)>>1,ans = -inf;
    if(ll <= mid) ans = max(ans,query_max(lson,l,mid,ll,rr));
    if(rr > mid) ans = max(ans,query_max(rson,mid+1,r,ll,rr));
    return ans;
}

int Query_max(int x,int y)
{
    int ans = -inf;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans = max(ans,query_max(1,1,n,tid[top[x]],tid[x]));
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    ans = max(ans,query_max(1,1,n,tid[x],tid[y]));
    return ans;
}

int query_sum(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) return sum[o];
    int mid = (l+r)>>1,ans = 0;
    if(ll <= mid) ans += query_sum(lson,l,mid,ll,rr);
    if(rr > mid) ans += query_sum(rson,mid+1,r,ll,rr);
    return ans;
}

int Query_sum(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans += query_sum(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    ans += query_sum(1,1,n,tid[x],tid[y]);
    return ans;
}

int main()
{
    int u,v,q;
    scanf("%d",&n);
    init();
    for(int i = 1;i < n;i++) {
        scanf("%d%d",&u,&v);
        add_edge(u,v); add_edge(v,u);
    }
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    dfs1(1,0,0); dfs2(1,1);
    build(1,1,n);

    char opt[10];
    scanf("%d",&q);
    while(q--) {
        scanf("%s%d%d",opt,&u,&v);
        if(opt[1] == 'M') printf("%d\n",Query_max(u,v));
        if(opt[1] == 'S') printf("%d\n",Query_sum(u,v));
        if(opt[1] == 'H') update(1,1,n,tid[u],v);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值