树链剖分

目录
  • 树链剖分
    • 1.算法分析
      • 1.1 重链剖分:
        • 1.1.1 定义
        • 1.1.2 重链剖分的性质
        • 1.1.3 常见应用
      • 1.2 长链剖分
        • 1.2.1 定义
        • 1.2.2 长链剖分性质
        • 1.2.3 长链剖分应用
    • 2. 板子
      • 2.1 重链剖分
        • 2.1.1 路径维护/子树维护/lca
      • 2.2 长链剖分
        • 2.2.1 求lca/求每条长链的长度
        • 2.2.2 查询一个点的k级祖先
        • 2.2.3 O(n)处理可合并的与深度有关的子树信息
    • 3. 例题
      • 3.1 重链剖分

树链剖分

1.算法分析

dfs1函数:这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0

1.1 重链剖分:

1.1.1 定义

    重子节点 表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。轻子节点 表示剩余的所有子结点。从这个结点到重子节点的边为 重边 。到其他轻子节点的边为 轻边 。若干条首尾衔接的重边构成 重链 。把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。

树链剖分_第1张图片

1.1.2 重链剖分的性质

    树上每个节点都属于且仅属于一条重链。 重链开头的结点不一定是重子节点(因为重边是对于每一个结点都有定义的)。所有的重链将整棵树 完全剖分 。在剖分时 优先遍历重儿子 ,最后重链的DFS序就会是连续的。在剖分时,重边优先遍历,最后树的 DFN 序上,重链内的DFN序是连续的。按DFN排序后的序列即为剖分后的链。一颗子树内的 DFN 序是连续的。可以发现,当我们向下经过一条 轻边 时,所在子树的大小至少会除以二。因此,对于树上的任意一条路径,把它拆分成从 分别向两边往下走,分别最多走O(logn)次,因此,树上的每条路径都可以被拆分成不超过 O(logn) 条重链。

1.1.3 常见应用

  1. 路径维护:用树链剖分求树上两点路径权值和。链上的 DFS 序是连续的,可以使用线段树、树状数组维护。每次选择深度较大的链往上跳,直到两点在同一条链上。同样的跳链结构适用于维护、统计路径上的其他信息。
  2. 子树维护:维护子树上的信息,譬如将以x为根的子树的所有结点的权值增加v。在 DFS 搜索的时候,子树中的结点的 DFS 序是连续的。每一个结点记录 bottom 表示所在子树连续区间末端的结点。这样就把子树信息转化为连续的一段区间信息。
  3. 求最近公共祖先:不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 LCA。

1.2 长链剖分

1.2.1 定义

长链剖分本质上就是另外一种链剖分方式。
定义 重子节点 表示其子节点中子树深度最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。
定义 轻子节点 表示剩余的子结点。
从这个结点到重子节点的边为 重边
到其他轻子节点的边为 轻边
若干条首尾衔接的重边构成 重链
把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。
如图(这种剖分方式既可以看成重链剖分也可以看成长链剖分):
树链剖分_第2张图片

1.2.2 长链剖分性质

  1. 任意点祖先所在长链长度一点大于等于这个点所在长链长度
  2. 所有长链长度之和就是总结点数
  3. 一个点到根节点的路径上经过的短边最多有 √n 条 (长儿子深度和短儿子深度相等时取到)

1.2.3 长链剖分应用

  1. O(1) 移动到链头 (求lca,和重链剖分一样)
  2. O(nlogn) 预处理,单次 O(1) 在线查询一个点的 k 级祖先
  3. O(n) 处理可合并的与深度有关的子树信息 (例如某深度点数、某深度点权和)

2. 板子

2.1 重链剖分

2.1.1 路径维护/子树维护/lca

luogu P3384 【模板】轻重链剖分
    一棵包含 N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。
操作 3: 格式: 3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z。
操作 4: 格式: 4 x 表示求以 x 为根节点的子树内所有节点值之和
操作有1e5个,树的节点数为1e5个

#include
using namespace std;

const int N=100000+10;

int tot, num;
int n, m, r, p;

int w[N], a[N], dat[N*4], lazy[N*4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N*2], ne[N*2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], size[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到size, fa, dep, wson数组
void dfs1(int u)
{
    dep[u]=dep[fa[u]]+1; 
    size[u]=1; 
    for(int i=h[u]; ~i; i=ne[i])
    {#include
using namespace std;

const int N = 100000+10;

int tot, num;
int n, m, r, p;

int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson数组
void dfs1(int u)
{
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = h[u]; ~i; i = ne[i])  // 然后搜索轻儿子
    {
        int y=e[i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
    dat[rt] %= p; 
}

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r)
{
    if(l==r)
    {
        dat[rt]=w[l]; 
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下传
void pushdown(int rt, int l, int r)
{
    if(lazy[rt])
    {
        int mid=(l + r)>>1; 
        dat[rt << 1] += lazy[rt]*(mid - l + 1), dat[rt << 1] %= p, lazy[rt << 1] += lazy[rt]; 
        dat[rt << 1 | 1] += lazy[rt]*(r-mid), dat[rt << 1 | 1] %= p, lazy[rt << 1 | 1] += lazy[rt]; 
        lazy[rt]=0; 
    }
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, l为需要修改的左区间,r为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int k)
{
    if(L <= l && r <= R)
    {
        dat[rt] += k*(r-l+1); 
        dat[rt] %= p; 
        lazy[rt] += k; 
        lazy[rt] %= p; 
        return ; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, k); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k); 
    pushup(rt);
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, l为需要查询的左区间,r为查询的右区间
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return dat[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p; 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p; 
    return ans; 
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y)
{
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        ans %= p; 
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans % p; 
}

// 将树从 x到 y 结点最短路径上所有节点的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
    //树上两点距离
    k %= p; 
    while(top[x] != top[y]) // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子树和
        x = fa[top[x]]; // x跳到原来x的头部的父节点
    }
    if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

// 求以 x 为根节点的子树内所有节点值之和
int Point_query_Tree(int rt)
{
    //由搜索序的特点可得,子树的搜索序一定比根大
    return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1); 
}

// 将以 x 为根节点的子树内所有节点值都加上 z
void Point_modify_Tree(int rt, int k)
{
    k %= p; 
    modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k); 
}

// 求u和v的lca
int lca(int u, int v) 
{
    while (top[u] != top[v]) 
    {
        if (dep[top[u]] > dep[top[v]])
            u = fa[top[u]];
        else
            v = fa[top[v]];
    }
    return dep[u] > dep[v] ? v : u;
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &r, &p);  // 读入点数、边数、根、模数

    for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 读入每个点的权值

    // 读入边,建树
    memset(h,  -1,  sizeof h); 
    for(int i=1, x, y; i

lugou P2486 [SDOI2011]染色
题意:
给定一棵 n 个节点的无根树,共有 m 个操作,操作分为两种:

  • 将节点 aa 到节点 bb 的路径上的所有点(包括 aa 和 bb)都染成颜色 cc。
  • 询问节点 aa 到节点 bb 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:11、222、1。
题解: 线段树+树剖,线段树维护颜色信息即可
以下代码没有调通

#include
using namespace std;

const int N=1e5 + 10;

int tot, num;
int n, m, r;

int w[N], a[N], lco[N * 4], rco[N * 4], dat[N*4], lazy[N*4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N*2], ne[N*2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 sz子树大小 top链头  fa父节点

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson数组
void dfs1(int u)
{
    dep[u] = dep[fa[u]] + 1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u]=j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = h[u]; ~i; i = ne[i])  // 然后搜索轻儿子
    {
        int y = e[i]; 
        if(y == fa[u] || y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    lco[rt] = lco[rt << 1] , rco[rt] = lco[rt << 1 | 1];
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
    if (rco[rt << 1] == lco[rt << 1 | 1]) dat[rt]--;
}

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r)
{
    if(l == r)
    {
        dat[rt] = 1; 
        lco[rt] = rco[rt] = w[l];
        lazy[rt] = 0;
        return ; 
    }
    int mid = (l + r) >> 1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid + 1, r); 
    pushup(rt);
}

void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {
        lazy[rt << 1] = lazy[rt];
        lazy[rt << 1 | 1] = lazy[rt];
        
        lco[rt << 1] = rco[rt << 1] = lazy[rt];
        lco[rt << 1 | 1] = rco[rt << 1 | 1] = lazy[rt];
        
        dat[rt << 1] = 1;
        dat[rt << 1 | 1] = 1;
        lazy[rt] = 0;
    } 
}

void modify(int rt, int l, int r, int L, int R, int c) {
    if (L <= l && r <= R) {
        dat[rt] = 1;
        lco[rt] = rco[rt] = c;
        lazy[rt] = c;
        return;
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, c);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, c);
    pushup(rt);
}

int query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    int res = 0, flg = 0;
    if (L <= mid) {
        res = query(rt << 1, l, mid, L, R), flg = 1;
    }
    if (mid < R) {
        res += query(rt << 1 | 1, mid + 1, r, L, R);
        if (flg == 1 && rco[rt << 1] == lco[rt << 1 | 1]) res--;
    }
    return res;
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y)
{
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        if (a[top[x]] == a[fa[top[x]]]) ans--;
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 将树从 x到 y 结点最短路径上所有节点的值都加上 z
void path_modify_Tree(int x, int y, int k)
{
    //树上两点距离
    while(top[x] != top[y]) // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子树和
        x = fa[top[x]]; // x跳到原来x的头部的父节点
    }
    if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

int main()
{
    scanf("%d%d", &n, &m);  // 读入点数、边数、根、模数
    r = 1;

    for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 读入每个点的权值

    // 读入边,建树
    memset(h,  -1,  sizeof h); 
    for(int i = 1, x, y; i < n; i++)
    {
        scanf("%d%d", &x, &y); 
        add(x, y); 
        add(y, x); 
    }

    // 两次dfs把树按照重链剖分
    dfs1(r);   // 得到sz, fa, dep, wson数组
    dfs2(r, r);   // 得到dfn, top数组
    build(1, 1, n); 
    
    // m次询问
    string op;
    for(int i = 1, x, y, z; i <= m; i++)
    {
        cin >> op;
        if (op[0] == 'Q') {
            scanf("%d %d", &x, &y);
            printf("%d\n", path_query_Tree(x, y));
        }
        else {
            scanf("%d%d%d", &x, &y, &z);
            path_modify_Tree(x, y, z);
        }
    }
    return 0;
}

2.2 长链剖分

2.2.1 求lca/求每条长链的长度

求lca

#include
using namespace std;

const int N=500000+10;

int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso长儿子,maxlen最大深度,fa父节点,dep深度,top链头
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;

void add(int a,int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 求fa,dep, maxlen
void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    maxlen[x]=dep[x]; 
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x])
        {
            fa[y]=x;
            dfs1(y);
            maxlen[x]=max(maxlen[y],maxlen[x]);
            if(maxlen[y]>maxlen[lson[x]]) lson[x]=y;  // 这里要注意根节点不能设为0,否则根节点的最长链无法更新,始终为0
        }
    }
}

// 求top
void dfs2(int x,int nowtop)
{
    top[x]=nowtop;
    if(lson[x]) dfs2(lson[x],nowtop);
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x]&&y!=lson[x]) dfs2(y,y);
    }
}

// 求lca
int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]

Benelux Algorithm Programming Contest 2019
A. Appeal to the Audience

题意: 给定一棵n点树,该树有m叶,每片叶子都有权值k,每片叶子能带来叶子所处的长链的长度*叶子权值k的收益,求最大收益。

题解: 长链的贪心性质
代码:

/*
本题需要让越大权值的运动员位于越长的链的底部,即可求出最大答案
只需要多维护一个数组len,记录每条链的长度即可
*/

#include
using namespace std;

typedef long long LL;
const int N=100000+10;

int lson[N], maxlen[N], fa[N], dep[N], top[N]; // lso长儿子,maxlen最大深度,fa父节点,dep深度,top链头
int h[N],e[N*2],ne[N*2], idx;
int n, m, root;
int a[N], len[N];  // len维护每一条链的长度

void add(int a,int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 求fa,dep, maxlen
void dfs1(int x)
{
    dep[x]=dep[fa[x]]+1;
    maxlen[x]=dep[x]; 
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x])
        {
            fa[y]=x;
            dfs1(y);
            maxlen[x]=max(maxlen[y],maxlen[x]);
            if(maxlen[y]>maxlen[lson[x]]) lson[x]=y;
        }
    }
}

// 求top
void dfs2(int x,int nowtop, int length)
{
    top[x]=nowtop;
	len[nowtop] = length + 1;  // 链头为nowtop的链的长度为length+1
    if(lson[x]) dfs2(lson[x],nowtop, len[nowtop]);
    for(int i=h[x];~i;i=ne[i])
    {
        int y=e[i];
        if(y!=fa[x]&&y!=lson[x]) dfs2(y,y, 0);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
    sort(a + 1, a + 1 + m);
    reverse(a + 1, a + 1 + m);
    memset(h, -1, sizeof h);
    for(int i=2, x;i<=n;++i)
    {
        cin >> x;
        x++;
        add(i, x), add(x, i);
    }

    root = 1;
    dfs1(root);  // 第一次求dep, fa, maxlen, lson
    dfs2(root,root, 0);  // 第二次求top

    set s;
    for (int i = 1; i <= n; ++i) s.insert(top[i]);
    LL ans = 0;
    int i = 0;
    vector v;
    for (auto si: s) v.push_back(len[si]);
    sort(v.begin(), v.end());
    reverse(v.begin(), v.end());
    for (int i = 0; i < m; ++i) 
    {
        if (i == 0) v[i]--;
        ans += a[i + 1] * (LL)(v[i]);
    }
    cout << ans << endl;

    return 0;
}

2.2.2 查询一个点的k级祖先

2.2.3 O(n)处理可合并的与深度有关的子树信息

3. 例题

3.1 重链剖分

P2590 [ZJOI2008]树的统计
题意:
一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点 u 的权值改为 t。
II. QMAX u v: 询问从点 u 到点 v 的路径上的节点的最大权值。
III. QSUM u v: 询问从点 u 到点 v 的路径上的节点的权值和。
注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。
题解:
只需要线段树加单点修改区间查询的线段树即可,模板题
代码:

#include
using namespace std;

const int N = 3e4 + 10, INF = 1e9 + 10;

int tot, num;
int n, m, r;

// dat1:sum, dat2:max
int w[N], a[N], dat1[N * 4], dat2[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson数组
void dfs1(int u)
{
    dep[u] = dep[fa[u]] + 1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = h[u]; ~i; i = ne[i])  // 然后搜索轻儿子
    {
        int y = e[i]; 
        if(y == fa[u] || y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat1[rt] = dat1[rt << 1] + dat1[rt << 1 | 1]; 
    dat2[rt] = max(dat2[rt << 1], dat2[rt << 1 | 1]);
}

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r)
{
    if(l == r)
    {
        dat1[rt] = dat2[rt] = w[l]; 
        return ; 
    }
    int mid = (l + r) >> 1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

void modify(int rt, int l, int r, int x, int y) {
    if (l == r) {
        dat1[rt] = y;
        dat2[rt] = y;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) modify(rt << 1, l, mid, x, y);
    else modify(rt << 1 | 1, mid + 1, r, x, y);
    pushup(rt);
}

int query1(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return dat1[rt]; 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query1(rt << 1, l, mid, L, R); 
    if(mid < R) ans += query1(rt << 1 | 1, mid + 1, r, L, R); 
    return ans; 
}

int query2(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat2[rt];
    int mid = (l + r) >> 1;
    int ans = -INF;
    if (L <= mid) ans = max(ans, query2(rt << 1, l, mid, L, R));
    if (mid < R) ans = max(ans, query2(rt << 1 | 1, mid + 1, r, L, R));
    return ans;
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree1(int x, int y)
{
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query1(1, 1, n, dfn[top[x]], dfn[x]);   
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query1(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 求树从 x 到 y 结点最短路径上所有节点的max
int path_query_Tree2(int x, int y)
{
    //两点间的修改
    int ans = -INF; 
    while(top[x] != top[y])  // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans = max(ans, query2(1, 1, n, dfn[top[x]], dfn[x]));   
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans = max(ans, query2(1, 1, n, dfn[x], dfn[y])); 
    return ans; 
}

void modify_Tree(int x, int y) {
    modify(1, 1, n, dfn[x], y);
}

int main()
{
    scanf("%d", &n);  // 读入点数、边数、根、模数
    r = 1;

    // 读入边,建树
    memset(h,  -1,  sizeof h); 
    for(int i = 1, x, y; i < n; i++)
    {
        scanf("%d%d", &x, &y); 
        add(x, y); 
        add(y, x); 
    }

    for(int i = 1; i <= n; i++) scanf("%d", &a[i]); // 读入每个点的权值
    
    scanf("%d", &m);
    
    // 两次dfs把树按照重链剖分
    dfs1(r);   // 得到sz, fa, dep, wson数组
    dfs2(r, r);   // 得到dfn, top数组
    build(1, 1, n); 

    // m次询问
    for(int i=1, u, v; i<=m; i++)
    {
        char op[10];
        scanf("%s", op);
        scanf("%d%d", &u, &v);
        if (op[0] == 'Q' && op[1] == 'S') printf("%d\n", path_query_Tree1(u, v));
        else if (op[0] == 'Q' && op[1] == 'M') printf("%d\n", path_query_Tree2(u, v));
        else modify_Tree(u, v);
    }

    return 0;
}

P2146 [NOI2015]软件包管理器
题意:
    你决定设计你自己的软件包管理器。不可避免地,你要解决软件包之间的依赖问题。如果软件包 a 依赖软件包 b,那么安装软件包 a 以前,必须先安装软件包 b。同时,如果想要卸载软件包 b,则必须卸载软件包 a。
    现在你已经获得了所有的软件包之间的依赖关系。而且,由于你之前的工作,除 0 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 0 号软件包不依赖任何一个软件包。且依赖关系不存在环
    现在你要为你的软件包管理器写一个依赖解决程序。根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。
    注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 0。
题解: 通过分析可以知道软件依赖关系是一棵树,当安装软件时,就是影响从根(0节点)到x;当卸载软件时,就是影响以x为根的子树。所以只需要线段树lazy维护区间染色,dat维护区间和即可。
代码:

#include
using namespace std;

const int N = 1e5 + 10;

int tot, num;
int n, m, r;

int w[N], a[N], dat[N * 4], lazy[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++; 
}

// 得到sz, fa, dep, wson数组
void dfs1(int u)
{
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j=e[i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组
void dfs2(int u, int nowtop)
{
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = h[u]; ~i; i = ne[i])  // 然后搜索轻儿子
    {
        int y=e[i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r)
{
    if(l==r)
    {
        dat[rt] = 0; 
        lazy[rt] = -1;
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下传
void pushdown(int rt, int l, int r)
{
    if (lazy[rt] == -1) return;
    int mid=(l + r)>>1; 
    dat[rt << 1] = lazy[rt]*(mid - l + 1), lazy[rt << 1] = lazy[rt]; 
    dat[rt << 1 | 1] = lazy[rt]*(r-mid), lazy[rt << 1 | 1] = lazy[rt]; 
    lazy[rt]=-1; 
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, l为需要修改的左区间,r为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int k)
{
    if(L <= l && r <= R)
    {
        dat[rt] = k*(r-l+1); 
        lazy[rt] = k; 
        return ; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, k); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, k); 
    pushup(rt);
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, l为需要查询的左区间,r为查询的右区间
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return dat[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R); 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R); 
    return ans; 
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y)
{
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y])  // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans; 
}

// 将树从 x到 y 结点最短路径上所有节点的值都赋值为 z
void path_modify_Tree(int x, int y, int k)
{
    //树上两点距离
    while(top[x] != top[y]) // 把x点和y点整到一条重链上
    {
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
        modify(1, 1, n, dfn[top[x]], dfn[x], k); // 累加x的所有子树和
        x = fa[top[x]]; // x跳到原来x的头部的父节点
    }
    if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
    modify(1, 1, n, dfn[x], dfn[y], k); 
}

// 求以 x 为根节点的子树内所有节点值之和
int Point_query_Tree(int rt)
{
    //由搜索序的特点可得,子树的搜索序一定比根大
    return query(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1); 
}

void Point_modify_Tree(int rt, int k)
{
    modify(1, 1, n, dfn[rt], dfn[rt]+sz[rt]-1, k); 
}

int main()
{
    scanf("%d", &n);
    r = 1;
    // 读入边,建树
    memset(h,  -1,  sizeof h); 
    for (int i = 2, x; i <= n; ++i) {
        scanf("%d", &x);
        x++;
        add(i, x), add(x, i);
    }

    scanf("%d", &m);

    // 两次dfs把树按照重链剖分
    dfs1(r);   // 得到sz, fa, dep, wson数组
    dfs2(r, r);   // 得到dfn, top数组
    build(1, 1, n); 

    // m次询问
    char op[10];
    for(int i=1, x; i<=m; i++)
    {
        scanf("%s", op);
        scanf("%d", &x);
        x++;
        if(op[0] == 'i')
        {
            int cnt1 = path_query_Tree(1, x);
            path_modify_Tree(1, x, 1); 
            printf("%d\n", path_query_Tree(1, x) - cnt1);
        }
        else 
        {
            int cnt1 = Point_query_Tree(x);
            Point_modify_Tree(x, 0);
            printf("%d\n", cnt1); //求树从 x 到 y 结点最短路径上所有节点的值之和
        }
    }
    return 0;
}

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