树链剖分+线段树

7 篇文章 0 订阅
3 篇文章 0 订阅

树链剖分

一树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。我们将以下面的形式来要求你对这棵树完成一些操作:

1.CHANGE u t :把节点 u 权值改为 t;
2.QMAX u v :询问点 u 到点 v 路径上的节点的最大权值;
3.QSUM u v :询问点 u 到点 v 路径上的节点的权值和。
注意:从点 u 到点 v 路径上的节点包括 u 和 v 本身。

输入格式

第一行为一个数 n,表示节点个数;

接下来 n−1 行,每行两个整数 a,b,表示节点 a 与节点 b 之间有一条边相连;

接下来一行 n 个整数,第 i 个整数 wi 表示节点 i 的权值;

接下来一行,为一个整数 q ,表示操作总数;

接下来 q 行,每行一个操作,以 CHANGE u t 或 QMAX u v 或 QSUM u v的形式给出。

输出格式
对于每个 QMAX 或 QSUM 的操作,每行输出一个整数表示要求的结果。

样例
Input Output
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

Output
4
1
2
2
10
6
5
6
5
16
数据范围与提示
对于 100% 的数据,有 1≤n≤3×104,0≤q≤2×105。中途操作中保证每个节点的权值 w 在 −30000 至 30000 之间。

原题来自:ZJOI 2008
题目链接

算是个树链剖分裸题吧
然后贴代码

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

using namespace std;

const int N = 30010, M = N * 2, INF = 0x3f3f3f3f;

struct Node{
    int l, r;
    int d, sum;
}tr[N * 4];

int h[N], e[M], ne[M], idx;
void add(int a, int b)//添加边a到b 
{
    e[idx] = b;//终点指向b 
    ne[idx] = h[a];
    h[a] = idx ++;//idx是边的总数
}

int n;
int w[N];
int f[N], dep[N], sz[N], son[N];        //  父节点,深度,子树大小,重儿子
void dfs1(int u, int fa)                //  当前节点,父节点
{
    f[u] = fa;
    dep[u] = dep[fa] + 1;
    sz[u] = 1;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == fa)  continue;//因为是无向边所以如果遇到父亲就continue 
        dfs1(j, u);
        sz[u] += sz[j];
        if(sz[j] > sz[son[u]])  son[u] = j;
    }
}

int top[N], id[N], rk[N], tot;          //  链顶,原编号对应的新编号,新编号对应的原编号
void dfs2(int u, int t)                 //  当前节点,链顶
{
    top[u] = t;
    id[u] = ++ tot;//建立dfs序 tot是按照dfs序的边的总数,这一步是把DFS序记录在原树的节点上 
    rk[tot] = u;// dfs序编号对应的原树的节点号 
    if(son[u])  dfs2(son[u], t);//如果存在中儿子那么先遍历重儿子给dfs序 
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j == f[u] or j == son[u])  continue;//重儿子已经被遍历过了所以父亲和重儿子直接过 
        dfs2(j, j);//注意重连的头是由轻儿子开始的 
    }
}

void build(int u, int l, int r)//建立以DFS序为区间的线段树 
{
    tr[u] = {l, r};
    if(l == r)
    {
        tr[u].d = tr[u].sum = w[rk[l]];
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid),  build(u << 1 | 1, mid + 1, r);
    tr[u].d = max(tr[2*u].d, tr[2*u+1].d);//更新最大值 
    tr[u].sum = tr[2*u].sum + tr[2*u+1].sum;//更新和 
}

void modify(int u, int x, int c)//更改权值,u是当前结点,由1开始,
//x是要更改节点对应的dfs序的值,c是要修改的值 这样就可以在线段树里面修正了 
{
    if(tr[u].l == x and tr[u].r == x)
    {
        tr[u].d = tr[u].sum = c;
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(x <= mid)  modify(u << 1, x, c);
    else  modify(u << 1 | 1, x, c);
    tr[u].d = max(tr[2*u].d, tr[2*u+1].d);//更新最大值 
    tr[u].sum = tr[2*u].sum + tr[2*u+1].sum;//更新和
}

int qmax(int u, int l, int r)//用线段树找所在区间最大值,由性质可知一条重连的dfs序是连续的 
{
    if(tr[u].l >= l and tr[u].r <= r)  return tr[u].d;

    int res = -INF;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)  res = max(res, qmax(u << 1, l, r));
    if(r > mid)  res = max(res, qmax(u << 1 | 1, l, r));
    return res;
}

int querymax(int a, int b)//询问原树上点a到b上最大值 
{
    int res = -INF;
    while(top[a] != top[b])//根节点不一样,说明不在同一重链上 
    {
        if(dep[top[a]] < dep[top[b]])  swap(a, b);//swap交换ab,是令top[a]比top[b]深
        res = max(res, qmax(1, id[top[a]], id[a]));//用线段树寻找根节点深度大的重链的max 
        a = f[top[a]];//top[a]是根节点,是个轻儿子,f[top[a]]是在另一条重重上的一个重儿子 
    }
    if(dep[a] > dep[b])  swap(a, b);        //  令a在b上面
    res = max(res, qmax(1, id[a], id[b]));//这时候a,b已经在一条重连上了,更新一下就行 
    return res;
}

int qsum(int u, int l, int r)//和上面的同理 
{
    if(tr[u].l >= l and tr[u].r <= r)  return tr[u].sum;

    int res = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)  res += qsum(u << 1, l, r);
    if(r > mid)  res += qsum(u << 1 | 1, l, r);
    return res;
}

int querysum(int a, int b)//同理 
{
    int res = 0;
    while(top[a] != top[b])
    {
        if(dep[top[a]] < dep[top[b]])  swap(a, b);      //  令top[a]比top[b]深
        res += qsum(1, id[top[a]], id[a]);
        a = f[top[a]];
    }
    if(dep[a] > dep[b])  swap(a, b);        //  令a在b上面
    res += qsum(1, id[a], id[b]);
    return res;
}

int main()
{
    memset(h, -1, sizeof h);

    scanf("%d", &n);
    for(int i = 1; i < n; i ++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);//无向边 
        add(b, a);
    }
    for(int i = 1; i <= n; i ++)  scanf("%d", &w[i]);

    dfs1(1, 0);
    dfs2(1, 1);
    build(1, 1, n);

    int m;
    scanf("%d", &m);
    while(m --)
    {
        char op[10];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if(op[0] == 'C')  modify(1, id[a], b);
        else if(op[1] == 'M')  printf("%d\n", querymax(a, b));
        else  printf("%d\n", querysum(a, b));
    }
    return 0;
}

2021.7.17

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
预处理 第一遍dfs求出树每个结点的深度deep[x],其为根的子树大小size[x] 以及祖先的信息fa[x][i]表示x往上距离为2^i的祖先 第二遍dfs ž根节点为起点,向下拓展构建重链 选择最大的一个子树的根继承当前重链 其余节点,都以该节点为起点向下重新拉一条重链 ž给每个结点分配一个位置编号,每条重链就相当于一段区间,用数据结构去维护。 把所有的重链首尾相接,放到同一个数据结构上,然后维护这一个整体即可 修改操作 ž1、单独修改一个点的权值 根据其编号直接在数据结构中修改就行了。 2、修改点u和点v的路径上的权值 (1)若u和v在同一条重链上 直接用数据结构修改pos[u]至pos[v]间的值。 (2)若u和v不在同一条重链上 一边进行修改,一边将u和v往同一条重链上靠,然后就变成了情况(1)。 伪代码 CHANGE (x, y ,d) while top[x]≠top[y] do if dep[top[x]]<dep[top[y]] then SWAP(x,y), SWAP (gx,gy) CHANGE-IT(tid[top[x]],tid[x],d) fa[x]→x if dep[x]>dep[y] then SWAP (x,y) CHANGE-IT(tid[x],tid[y],d) //CHANGE-IT(l,r,d)为数据结构的修改操作:将区间[l,r]上的所有权值改为d 查询操作 ž查询操作的分析过程同修改操作 伪代码 QUERY (x, y) while top[x]≠top[y] do if dep[top[x]]<dep[top[y]] then SWAP (x,y), SWAP (gx,gy) QUERY-IT(tid[top[x]],tid[x]) fa[x]→x if dep[x]>dep[y] then SWAP (x,y) QUERY-IT(tid[x],tid[y]) //QUERY-IT(l,r)为数据结构的查询操作, 题目不同,选用不同的数据结构来维护值,通常有线段树和splay [2]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值