[ZJOI2008]树的统计Count

本文详细介绍了一种高效处理树形结构数据的算法——树链剖分,通过两次深度优先搜索构建重链,并利用线段树进行快速查询与更新操作。适用于解决涉及树上路径最大值及路径和的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,表示节点的个数。接下来 n1 行,每行2个整数 a b,表示节点 a 和节点b之间有一条边相连。接下来 n 行,每行一个整数,第i行的整数 wi 表示节点i的权值。接下来1行,为一个整数 q ,表示操作的总数。接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
对于100%的数据,保证 1<=n<=30000 0<=q<=200000 ;中途操作中保证每个节点的权值 w 在-30000到30000之间。

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

HINT

Source

思路
树链剖分模板。
树链剖分不会的请点击这里

代码

#include <cstdio>
#include <algorithm>

const int maxn=30000;
const int inf=1000000000;

int n;

struct sigment_tree
{
    private:int maxnum[(maxn<<2)+10],sumnum[(maxn<<2)+10];

    private:int updata(int now)
    {
        maxnum[now]=std::max(maxnum[now<<1],maxnum[now<<1|1]);
        sumnum[now]=sumnum[now<<1]+sumnum[now<<1|1];
        return 0;
    }

    public:int build(int now,int left,int right)
    {
        if(left==right)
        {
            maxnum[now]=0;
            sumnum[now]=0;
            return 0;
        }
        int mid=(left+right)>>1;
        build(now<<1,left,mid);
        build(now<<1|1,mid+1,right);
        updata(now);
        return 0;
    }

    public:int modify(int now,int left,int right,int findnum,int changeval)
    {
        if((findnum<left)||(findnum>right))
        {
            return 0;
        }
        if(left==right)
        {
            maxnum[now]=changeval;
            sumnum[now]=changeval;
            return 0;
        }
        int mid=(left+right)>>1;
        modify(now<<1,left,mid,findnum,changeval);
        modify(now<<1|1,mid+1,right,findnum,changeval);
        updata(now);
        return 0;
    }

    public:int askmax(int now,int left,int right,int askl,int askr)
    {
        if((askr<left)||(askl>right))
        {
            return -inf;
        }
        if((askl<=left)&&(askr>=right))
        {
            return maxnum[now];
        }
        int mid=(left+right)>>1;
        return std::max(askmax(now<<1,left,mid,askl,askr),askmax(now<<1|1,mid+1,right,askl,askr));
    }

    public:int asksum(int now,int left,int right,int askl,int askr)
    {
        if((askr<left)||(askl>right))
        {
            return 0;
        }
        if((askl<=left)&&(askr>=right))
        {
            return sumnum[now];
        }
        int mid=(left+right)>>1;
        return asksum(now<<1,left,mid,askl,askr)+asksum(now<<1|1,mid+1,right,askl,askr);
    }
};

struct tree
{
    private:int fa[maxn+10],wson[maxn+10],top[maxn+10],dfn[maxn+10],cnt,deep[maxn+10],size[maxn+10];
    private:int pre[(maxn<<1)+10],now[maxn+10],son[(maxn<<1)+10],tot;
    private:sigment_tree st;

    public:int ins(int a,int b)
    //将a和b连一条有向边
    {
        tot++;
        pre[tot]=now[a];
        now[a]=tot;
        son[tot]=b;
        return 0;
    }

    public:int first_dfs(int u,int father)
    //首次dfs,将deep、fa、size、wson求出来,为第二次dfs做准备
    {
        deep[u]=deep[father]+1;
        fa[u]=father;
        size[u]=1;
        wson[u]=0;
        int j=now[u];
        while(j)
        {
            int v=son[j];
            if(v!=father)
            {
                first_dfs(v,u);
                size[u]+=size[v];
                if((!wson[u])||(size[v]>size[wson[u]]))
                {
                    wson[u]=v;
                }
            }
            j=pre[j];
        }
        return 0;
    }

    public:int second_dfs(int u,int father,int topfather)
    //第二次dfs,将dfn和top求出来
    {
        cnt++;
        dfn[u]=cnt;
        top[u]=topfather;
        if(wson[u])
        //重儿子的top就是当前节点的top
        {
            second_dfs(wson[u],u,topfather);
        }
        int j=now[u];
        while(j)
        {
            int v=son[j];
            if((v!=father)&&(v!=wson[u]))
            {
                second_dfs(v,u,v);
            }
            j=pre[j];
        }
        return 0;
    }

    public:int change(int pos,int val)
    //单点修改
    {
        st.modify(1,1,n,dfn[pos],val);
        //直接修改线段树上这个节点就好
        return 0;
    }

    public:int askmax(int x,int y)
    //一条路径上求max值
    {
        int res=-inf;
        while(top[x]!=top[y])
        {
            if(deep[top[x]]<deep[top[y]])
            {
                std::swap(x,y);
            }
            res=std::max(res,st.askmax(1,1,n,dfn[top[x]],dfn[x]));
            x=fa[top[x]];
        }
        if(deep[x]>deep[y])
        {
            std::swap(x,y);
        }
        //top值相同,那么x和y在同一条重链上
        res=std::max(res,st.askmax(1,1,n,dfn[x],dfn[y]));
        return res;
    }

    public:int asksum(int x,int y)
    //一条路径上求sum值
    {
        int res=0;
        while(top[x]!=top[y])
        {
            if(deep[top[x]]<deep[top[y]])
            {
                std::swap(x,y);
            }
            res+=st.asksum(1,1,n,dfn[top[x]],dfn[x]);
            x=fa[top[x]];
        }
        if(deep[x]>deep[y])
        {
            std::swap(x,y);
        }
        res+=st.asksum(1,1,n,dfn[x],dfn[y]);
        return res;
    }
};

tree t;
int m;

int main()
{
    scanf("%d",&n);
    for(int i=1; i<n; i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        t.ins(a,b);
        t.ins(b,a);
    }
    t.first_dfs(1,0);
    t.second_dfs(1,0,1);
    for(int i=1; i<=n; i++)
    {
        int a;
        scanf("%d",&a);
        t.change(i,a);
    }
    scanf("%d",&m);
    while(m--)
    {
        char s[7];
        int a,b;
        scanf("%s%d%d",s,&a,&b);
        if(s[1]=='H')
        {
            t.change(a,b);
        }
        if(s[1]=='S')
        {
            printf("%d\n",t.asksum(a,b));
        }
        if(s[1]=='M')
        {
            printf("%d\n",t.askmax(a,b));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值