树的统计

算是树剖模板题,两边dfs加线段树可以解决。
#include <iostream>

#include <cmath>

#include <algorithm>

#include <cstring>

#include <cstdio>

using namespace std;

#define xx 100005

int size[xx], wson[xx], dep[xx], fa[xx], top[xx];
//x节点的节点数,重儿子,深度,父亲,x所在重链的链头。 

int tops[xx], pre[xx], cnt, ta;//dfs序,dfs序的反映射,cnt,ta计数用。 

int w[4*xx], n, q; //节点权值,节点数,操作数。 

string ad;//操作指令。 

struct re
{
    int next,head,t;
}e[xx<<2];//存图 。 

struct ak
{
    int sum,maxv,r,l;
}tree[xx<<2];//存线段树。 

void add(int x,int y)
{

    e[++ta].t=y;e[ta].next=e[x].head;e[x].head=ta;
    e[++ta].t=x;e[ta].next=e[y].head;e[y].head=ta;//建图。
} 

void dfs1(int now,int f)
{

    size[now]=1;//dfs每个点的子节点数,深度,重链所在节点的重儿子, 
    for(int i=e[now].head;i;i=e[i].next)
    {
        int go=e[i].t;
        if(go!=f)//因为是双向图,所以要防止无限递归爆栈。 
        {
            dep[go]=dep[now]+1;fa[go]=now;
            dfs1(go,now);
            size[now]+=size[go];
            if(size[go]>size[wson[now]])
            wson[now]=go;
            //如果go的子节点数比父亲当前的重儿子子节点数多,则go替换成为重儿子。 
        }
    }
}
void dfs2(int now,int tp)
{

    tops[now]=++cnt; pre[cnt]=now; top[now]=tp;//记录所有节点的dfs序及其反映射。 
    if(wson[now]) dfs2( wson[now],tp );//如果有重儿子则继续编号。 
    for(int i=e[now].head;i;i=e[i].next)
    {
        int go=e[i].t;
        if(go!=fa[now]&&go!=wson[now])
        {
            dfs2(go,go);//再去拉伸除重儿子外的节点的子节点下的重链。 
        }
    }
}
void build(int d,int l,int r)
{

    int mid=(l+r)/2;//线段树建树(dfs序的线段树)。 
    tree[d].l=l;tree[d].r=r;
    if(l==r)
    {
        tree[d].sum=tree[d].maxv=w[pre[l]];//不同点在是w[pre[l]]不是w[l]。 
        return;
    }
    build(d*2,l,mid);build(d*2+1,mid+1,r);
    tree[d].sum=tree[d*2].sum+tree[d*2+1].sum;
    tree[d].maxv=max(tree[d*2].maxv,tree[d*2+1].maxv);
}
void update(int d,int x,int z)
{
//线段树单点修改操作。 

    int mid=(tree[d].l +tree[d].r )/2;
    if(tree[d].l==tree[d].r)
    {
        tree[d].sum=tree[d].maxv=z;
        return;
    }
    if(x>mid)
    {
        update(d*2+1,x,z);
    }
    else
    {
        update(d*2,x,z);
    }
    tree[d].sum=tree[d*2].sum+tree[d*2+1].sum;
    tree[d].maxv=max(tree[d*2].maxv,tree[d*2+1].maxv);
    //修改受影响区间的sum和maxv。 
}

int treesum(int d,int l,int r,int ql,int qr)
{

    int mid=(l+r)/2,ans=0;
    if (ql<=l&&r<=qr)return tree[d].sum;
    if (ql<=mid)ans+=treesum(d*2,l,mid,ql,qr);
    if (qr>mid)ans+=treesum(d*2+1,mid+1,r,ql,qr);
    return ans;
}
int treemaxv(int d,int ql,int qr)
{

    int mid=(tree[d].l+tree[d].r)/2,ans=-1000000;
    if (ql<=tree[d].l&&tree[d].r<=qr)return tree[d].maxv;
    if (ql<=mid)ans=max(ans,treemaxv(d*2,ql,qr));
    if (qr>mid)ans=max(ans,treemaxv(d*2+1,ql,qr));
    return ans;
}

int psum(int x,int y)
{

    int ans=0;
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);//使x存深度较大的节点的序号(是原序号,不是dfs序)。 
        ans+=treesum(1,1,n,tops[top[x]],tops[x]);
        //寻找x顶节点dfs序到x节点dfs序的区间和。 
        x=fa[top[x]];//x赋值为根节点的父亲节点。 
    }
    if(dep[x]<dep[y]) swap(x,y);
    ans+=treesum(1,1,n,tops[y],tops[x]);//如果根节点相同,直接计算。 
    return ans;
}
int pmax(int x,int y )
{

    int ans=-1000000000;//和求和代码很相似,不解释了。 
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        ans=max(ans,treemaxv(1,tops[top[x]],tops[x]));
        x=fa[top[x]];
    }
    if(dep[x]<dep[y]) swap(x,y);
    ans=max(ans,treemaxv(1,tops[y],tops[x]));
    return ans;
}
int main()
{

    cin>>n;
    for(int i=1;i<=n-1;++i)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);//函数直接写的存双向图。 
    }
    for(int i=1;i<=n;++i)
    {
        cin>>w[i];
    }
    dep[1]=1;fa[1]=1;dfs1(1,-1);dfs2(1,1);build(1,1,n);
    cin>>q;
    for(int i=1;i<=q;++i)
    {
        int x,y;cin>>ad;cin>>x>>y;
        if(ad[1]=='H') update(1,tops[x],y);
        if(ad[1]=='M') cout<<pmax(x,y)<<'\n';
        if(ad[1]=='S') cout<<psum(x,y)<<'\n';
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值