• Luogu 2590 [ZJOI2008]树的统计 / HYSBZ 1036 [ZJOI2008]树的统计Count (树链剖分,LCA,线段树)


    Luogu 2590 [ZJOI2008]树的统计 / HYSBZ 1036 [ZJOI2008]树的统计Count (树链剖分,LCA,线段树)

    Description

    一棵树上有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本身

    Input

    输入文件的第一行为一个整数n,表示节点的个数。
    接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。
    接下来一行n个整数,第i个整数wi表示节点i的权值。
    接下来1行,为一个整数q,表示操作的总数。
    接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。

    Output

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

    Sample Input

    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

    Sample Output

    4
    1
    2
    2
    10
    6
    5
    6
    5
    16

    Http

    Luogu:https://www.luogu.org/problem/show?pid=2590
    HYSBZ:http://www.lydsy.com/JudgeOnline/problem.php?id=1036

    Source

    树链剖分,最近公共祖先LCA,线段树

    解决思路

    这是一道树链剖分入门题。
    考虑将树按照子树大小剖分成轻链和重链,然后用线段树来维护区间和和区间最大值。
    需要注意的是,在树上进行链的跳转时,使用原编号,而若要用线段树查询或修改线段树时,要使用线段树中的编号(即第2次dfs中给点新分配的编号)

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    #define ll long long
    
    const int maxN=350000;
    const int inf=2147483647;
    
    class Edge//边
    {
    public:
        int u,v,nex;
    };
    
    class SegmentTree//线段树
    {
    public:
        ll mx,sum;//维护最大值和区间和
        SegmentTree()
            {
                mx=0;
                sum=0;
            }
    };
    
    class Tree_Chain//树链剖分中需要维护的一些值
    {
    public:
        int f,sz,top,hson,depth;//父节点,大小,向上跳的最高位置,重儿子,深度
    };
    
    int n;
    //Tree 原树的一些数据
    int cnt;
    int Head[maxN];
    Edge E[maxN*2];
    ll W[maxN];//点权
    Tree_Chain T[maxN];//树链剖分维护的值
    int dfn[maxN];//记录树链剖分后的点的新编号(按照先重链再轻链)
    SegmentTree Seg[maxN*4];//线段树
    
    void Add_Edge(int u,int v);
    //Tree_Chain
    void dfs1(int u,int father);//第一遍dfs求出T中的一些数据
    void dfs2(int u,int Top);//第二遍dfs构造Top和分配新编号
    ll LCA_sum(int u,int v);//求解区间和
    ll LCA_max(int u,int v);//求解区间最大值
    //SegmentTree
    void Update(int l,int r,int now,int pos,int key);//线段树单点更新
    ll Query_sum(int l,int r,int now,int ql,int qr);//查询区间和
    ll Query_max(int l,int r,int now,int ql,int qr);//查询区间最大值
    
    int main()
    {
        cnt=0;
        memset(Head,-1,sizeof(Head));
        scanf("%d",&n);
        for (int i=1;i<n;i++)//输入
        {
            int u,v;
            scanf("%d%d",&u,&v);
            Add_Edge(u,v);
            Add_Edge(v,u);
        }
        T[1].f=0;//默认1为根节点
        T[1].depth=1;
        dfs1(1,1);//第一遍dfs
        cnt=0;//标号置为0
        dfs2(1,1);//第二遍dfs
        for (int i=1;i<=n;i++)//输入点权
            scanf("%lld",&W[i]);
        for (int i=1;i<=n;i++)
            Update(1,n,1,dfn[i],W[i]);//将点权按照新编号放入线段树
        int qus;
        scanf("%d",&qus);
        while (qus--)//处理操作
        {
            char str[10];
            scanf("%s",str);
            if (str[0]=='Q')
            {
                int u,v;
                scanf("%d%d",&u,&v);
                if (str[1]=='M')
                    printf("%lld
    ",LCA_max(u,v));//查询最大值
                else
                    printf("%lld
    ",LCA_sum(u,v));//查询和
            }
            else
            {
                int pos,key;
                scanf("%d%d",&pos,&key);
                pos=dfn[pos];
                Update(1,n,1,pos,key);//单点修改
            }
        }
        return 0;
    }
    
    void Add_Edge(int u,int v)//在图中加一条边
    {
        cnt++;
        E[cnt].nex=Head[u];
        Head[u]=cnt;
        E[cnt].u=u;
        E[cnt].v=v;
        return;
    }
    
    void dfs1(int u,int father)//第一遍dfs
    {
        T[u].sz=1;//初始化基本信息
        T[u].hson=0;
        for (int i=Head[u];i!=-1;i=E[i].nex)
        {
            int v=E[i].v;
            if (v==father)
                continue;
            T[v].f=u;//将子节点信息更新
            T[v].depth=T[u].depth+1;
            dfs1(v,u);
            T[u].sz=T[u].sz+T[v].sz;
            if ((T[u].hson==0)||(T[T[u].hson].sz<T[v].sz))//求出重儿子
                T[u].hson=v;
        }
        return;
    }
    
    void dfs2(int u,int Top)//第二遍dfs,Top是当前传下来的能到达的重链顶端,当u==Top时,为轻链
    {
        cnt++;
        dfn[u]=cnt;//分配新编号
        T[u].top=Top;//分配Top
        if (T[u].hson==0)//没有重儿子,说明到了叶子节点
            return;
        dfs2(T[u].hson,Top);//让重儿子继承重链
        for (int i=Head[u];i!=-1;i=E[i].nex)
        {
            int v=E[i].v;
            if ((v==T[u].f)||(v==T[u].hson))
                continue;
            dfs2(v,v);//其他儿子开轻链
        }
        return;
    }
    
    ll LCA_max(int u,int v)//求解u到v路径上的最大点权
    {
        ll mx=-inf;
        while (T[u].top!=T[v].top)
        {
            if (T[T[u].top].depth<T[T[v].top].depth)//保证每一次u的深度尽量大
                swap(u,v);
            mx=max(mx,Query_max(1,n,1,dfn[T[u].top],dfn[u]));//让深度大的向上跳,注意这里要转成线段树中的编号
            u=T[T[u].top].f;
        }
        if (dfn[v]<dfn[u])//保证u的深度更小
            swap(u,v);
        mx=max(mx,Query_max(1,n,1,dfn[u],dfn[v]));//查询链
        return mx;
    }
    
    ll LCA_sum(int u,int v)//求解u到v上的点权之和,与上面基本一样
    {
        ll sum=0;
        while (T[u].top!=T[v].top)
        {
            if (T[T[u].top].depth<T[T[v].top].depth)
                swap(u,v);
            sum=sum+Query_sum(1,n,1,dfn[T[u].top],dfn[u]);
            u=T[T[u].top].f;
        }
        if (dfn[v]<dfn[u])
            swap(u,v);
        sum=sum+Query_sum(1,n,1,dfn[u],dfn[v]);
        return sum;
    }
    
    void Update(int l,int r,int now,int pos,int key)//线段树单点修改
    {
        if (l==r)
        {
            Seg[now].mx=Seg[now].sum=key;
            return;
        }
        int mid=(l+r)/2;
        if (pos<=mid)
            Update(l,mid,now*2,pos,key);
        if (pos>=mid+1)
            Update(mid+1,r,now*2+1,pos,key);
        Seg[now].sum=Seg[now*2].sum+Seg[now*2+1].sum;
        Seg[now].mx=max(Seg[now*2].mx,Seg[now*2+1].mx);
        return;
    }
    
    ll Query_sum(int l,int r,int now,int ql,int qr)//线段树查询区间和
    {
        if ((l==ql)&&(r==qr))
            return Seg[now].sum;
        int mid=(l+r)/2;
        if (qr<=mid)
            return Query_sum(l,mid,now*2,ql,qr);
        else if (ql>=mid+1)
            return Query_sum(mid+1,r,now*2+1,ql,qr);
        else
            return Query_sum(l,mid,now*2,ql,mid)+Query_sum(mid+1,r,now*2+1,mid+1,qr);
    }
    
    ll Query_max(int l,int r,int now,int ql,int qr)//线段树查询区间最大值
    {
        if ((l==ql)&&(r==qr))
            return Seg[now].mx;
        int mid=(l+r)/2;
        if (qr<=mid)
            return Query_max(l,mid,now*2,ql,qr);
        if (ql>=mid+1)
            return Query_max(mid+1,r,now*2+1,ql,qr);
        return max(Query_max(l,mid,now*2,ql,mid),Query_max(mid+1,r,now*2+1,mid+1,qr));
    }
    
  • 相关阅读:
    标准Gitlab命令行操作指导
    ssh登录巨慢加速验证
    ssh远程主机执行命令或脚本
    ssh创建与添加密钥开启免密登陆 免确认机器指纹参数
    linux免密传输文件 nc
    Linux 查看实时网卡流量的方法 网速 nload sar iftop dstat
    Mysql 常用命令
    Redis 常用命令整理
    shell sed -i 指定内容追加.
    java 复制指定目录中的所有文件和文件夹到另一个指定文件夹中
  • 原文地址:https://www.cnblogs.com/SYCstudio/p/7629164.html
Copyright © 2020-2023  润新知