专题三十一 树链剖分

专题三十一 树链剖分

HDU 3966

题意: 题意:一颗树上,每个点有权值,定义三种操作:

1)I操作表示从a到b节点之间的节点都加上一个值

2)D操作表示从a到b节点之间的节点的都减去一个权值

3)Q操作询问a节点当前的值。

题解: 树剖+线段树裸题。

代码:

#include <bits/stdc++.h>

using namespace std;

#define int long long
const int N = 50000 + 10;

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

inline int read() {
   
   int s = 0, w = 1;
   char ch = getchar();
   while (ch < '0' || ch > '9') {
   if (ch == '-') w = -1; ch = getchar();}
   while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
   return s * w;
}

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], Q;  //  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] = 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),
            lazy[rt << 1] += lazy[rt];
        dat[rt << 1 | 1] += lazy[rt] * (r - mid),
            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);
        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);
}

signed main() {
   
    while(scanf("%lld%lld%lld", &n, &m, &Q) != EOF) {
   
        for (int i = 1; i <= n; ++i) h[i] = -1, wson[i] = 0;
        memset(lazy, 0, sizeof lazy);
        idx = num = 0;
        r = 1;

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

        // 读入边,建树
        for (int i = 0, x, y; i < m; i++) {
   
            x = read(), y = read();
            add(x, y);
            add(y, x);
        }
        // 两次dfs把树按照重链剖分
        dfs1(r);     // 得到sz, fa, dep, wson数组
        dfs2(r, r);  // 得到dfn, top数组
        build(1, 1, n);

        // m次询问
        // cout << n << " " << m << " " << Q << endl;
        char op[2];
        for (int i = 1, x, y, z; i <= Q; i++) {
   
            scanf("%s", op);
            if (op[0] == 'I') {
   
                scanf("%lld%lld%lld", &x, &y, &z);
                path_modify_Tree(
                    x, y, z);  // 将树从 x到 y 结点最短路径上所有节点的值都加上 z。
            } else if (op[0] == 'Q') {
   
                scanf("%lld", &x);
                printf("%lld\n",
                    path_query_Tree(
                        x, x));  //求树从 x 到 y 结点最短路径上所有节点的值之和
            } else if (op[0] == 'D') {
   
                scanf("%lld%lld%lld", &x, &y, &z);
                path_modify_Tree(
                    x, y, -z);  // 将树从 x到 y 结点最短路径上所有节点的值都加上 z。
            }
        }
    }

    return 0;
}

POJ 2763 Housewife Wind

题意: 有一个人在一棵树的某个节点s,然后给出了树的每条边都有一个权值,有两种操作:

0 a 是问从节点s到a的路径权值和,然后a就成了s;

1 a b把第a条边权值变为b。 1 < = n < = 1 0 5 1<= n<=10^5 1<=n<=105

题解: 边权转换为点权,然后套上线段树。

代码:

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

#define int long long
using namespace std;

const int N = 100001 + 10;

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

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], w2[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, int c) {
   
    e[idx] = b, w2[idx] = c, 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; 
    //以搜索序重排权值
    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]=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), lazy[rt << 1] = lazy[rt
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值