bzoj 1036 [ZJOI2008]树的统计Count

本文介绍了一种结合树链剖分和线段树的数据结构应用案例。通过将树上的路径分解为重链和轻链,利用线段树进行高效查询与更新操作。文章详细阐述了树链剖分的基本概念、关键数据结构定义及实现细节。

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

第一道!
半YY的树剖+线段树!
我以后再也不写了!额。。还是得写、

写的时间不算很长?除了RE调了半天,别的还挺正常的。

然后简单说说
【树链剖分的概念】 树链,就是树上的路径。剖分,就是把路径分类为 重链 和 轻链 。树链剖分就是把一些点合成一条路径,使其在线段树中的编号(下标)有序,并用线段树来维护,使得查询、修改的效率大大提高(有点像莫队的分块思想)。假设我们把路径分好链了 (先不要在乎是怎么分的),每次询问两个点对(x,y)时,若x和y在同一链中,直接询问线段树中的u和v(因为同一条链中下标是连续的)u,v是x,y对应的线段树中的点。否则的话,我们从深度大的点上一点一点向上爬,每次记录该点所在的链上的情况,直到x,y在同一条链上。

【注意】树链剖分中的线段树中每个点代表的意义可以是原图的边或点。这道题是点,我就以点来叙述。

【数组含义简介】记num[v]表示以v为根的子树的节点数,deep[v]表示v的深度(根深度为1),top[v]表示v所在的链的顶端节点,f[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为 重儿子 ),tree[v]表示节点v在线段树中的编号,pre[v]表示线段树中编号是v的节点所对应的原图中的点(与tree相反)

只要把这些东西求出来,就能用logn的时间完成原问题中的操作。

【术语解释】
重儿子 :num[u]为v的子节点中num值最大的,那么u就是v的重儿子。
轻儿子 :v的其它子节点。
重边 :点v与其重儿子的连边。
轻边 :点v与其轻儿子的连边。
重链 :由重边连成的路径。
轻链: 轻边。
剖分后的树有如下性质:
性质1: 如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2: 从根到某一点的路径上轻链、重链的个数都不大于logn。

所以操作呢?

经典2遍dfs:
我们可以用两个dfs来求出fa、deep、num、son、top、tree、pre。
dfs_1:把 fa、deep、num、son 求出来,比较简单。
dfs_2:①我们依次标记tree[v](按搜索的顺序),同时得到pre。

       ②对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。(没有就退出)

       ③然后我们先搜索v的重儿子u,并把u的重儿子、重孙子...的top值也置为top[v];

       ④接着我们再搜索v的轻儿子u,并把u的 重儿子、重孙子...的top值置为u;

将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

然后是查询修改:
基本基于线段树的。
例如将u到v的路径上每条边的权值都加上某值x。
记f1=top[u],f2=top[v]。
当f1<>f2时:不妨设dep[f1]>=dep[f2],那么就更新u到f1的权值(logn),并使u=f[f1]。
当f1=f2时:u与v在同一条重链上,直接更新u到v路径上的点的权值(logn),修改完成;
重复上述过程,直到修改完成。
其实就是类似求lca吗。。

所以222行打代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 30005
#define ll long long
using namespace std;
//by mars_ch
int n,q;
int son[maxn],tree[maxn],pre[maxn],deep[maxn],num[maxn],f[maxn],top[maxn],vis[maxn]; 
ll w[maxn];
struct data
{
    int f,t,nxt;
}e[maxn*8];
struct node
{
    int l,r;
    int maxx;
    ll sum;
}tr[maxn*100];
int first[maxn*2],tot,tol;
void add(int a,int b)
{
    e[tot].f=a;
    e[tot].t=b;
    e[tot].nxt=first[a];
    first[a]=tot++;
}
void dfs1(int x)
{
    num[x]=1;
    for(int i=first[x];i!=-1;i=e[i].nxt)
    {
        int v=e[i].t;
        if(vis[v]) continue;
        vis[v]=1;
        deep[v]=deep[x]+1;
        f[v]=x;

        dfs1(v);
        num[x]+=num[v];
        if(!son[x] || num[v]>num[son[x]]) son[x]=v;
    }
}
void dfs2(int x,int num)
{
    top[x]=num;
    tree[x]=++tol;
    pre[tol]=x;

    if(!son[x]) return;
    dfs2(son[x],num);
    for(int i=first[x];i!=-1;i=e[i].nxt)
    {
        int t=e[i].t;
        if(t!=son[x]&&t!=f[x])
        {
            dfs2(t,t);      //递归轻儿子 
        }
    }
}
void build(int l,int r,int root)
{
    tr[root].l=l,tr[root].r=r;
    if(l == r)
    {
        tr[root].sum=(ll)w[pre[l]];
        tr[root].maxx=w[pre[l]];
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,root*2);
    build(mid+1,r,root*2+1);

    tr[root].sum=tr[root*2].sum+tr[root*2+1].sum;
    tr[root].maxx=max(tr[root*2].maxx,tr[root*2+1].maxx);
}
void update(int l,int r,int x,int c,int root)
{
    if(tree[x] == l && tree[x] == r)
    {
        tr[root].sum+=(ll)c;
        tr[root].maxx+=c;

        return;
    }
    int mid=(l+r)/2;
    if(tree[x]<=mid) update(l,mid,x,c,root*2);
    if(tree[x]>mid) update(mid+1,r,x,c,root*2+1);

    tr[root].sum=tr[root*2].sum+tr[root*2+1].sum;
    tr[root].maxx=max(tr[root*2].maxx,tr[root*2+1].maxx);
}
ll getsum(int x,int y,int root)
{
    //printf("%d %d %d\n",tr[root].l,tr[root].r,root); 
    if(tr[root].l>=x && tr[root].r<=y)
    {
        //printf("%d\n",tr[root].sum);
        return tr[root].sum;
    }   
    int mid=(tr[root].l+tr[root].r)/2;
    ll res=0;
    if(x<=mid) res+=getsum(x,y,root*2);
    if(y>mid) res+=getsum(x,y,root*2+1);

    return res; 
}
int getmaxx(int x,int y,int root)
{
    if(tr[root].l>=x && tr[root].r<=y)
    {
        return tr[root].maxx;
    }   
    int mid=(tr[root].l+tr[root].r)/2;
    int res=-100000000;
    if(x<=mid) res=max(res,getmaxx(x,y,root*2));
    if(y>mid) res=max(res,getmaxx(x,y,root*2+1));
    return res; 
}
ll find_sum(int x,int y)
{
    ll ans=0;
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(deep[fx]<deep[fy])
        {
            swap(fx,fy);
            swap(x,y);
        }
        ans+=getsum(tree[fx],tree[x],1);
        x=f[fx],fx=top[x];
    }
    if(deep[x]>deep[y])
    {
        ans+=getsum(tree[y],tree[x],1);
    }
    else ans+=getsum(tree[x],tree[y],1);

    return ans;
}
int find_maxx(int x,int y)
{
    int ans=-100000000;
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(deep[fx]<deep[fy])
        {
            swap(fx,fy);
            swap(x,y);
        }
        ans=max(ans,getmaxx(tree[fx],tree[x],1));
        x=f[fx],fx=top[x];
    }
    if(deep[x]>deep[y])
    {
        ans=max(ans,getmaxx(tree[y],tree[x],1));
    }
    else ans=max(ans,getmaxx(tree[x],tree[y],1));

    return ans;
}
signed main()
{
    scanf("%d",&n);
    memset(first,-1,sizeof(first));
    for(int i=1;i<=n-1;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&w[i]);
    }
    vis[1]=1;
    dfs1(1);
    dfs2(1,1); 
    build(1,n,1);
    scanf("%d",&q);
    while(q--)
    {
        char op[15];
        scanf("%s",op);
        if(op[0] == 'C')
        {
            int x,y;
            scanf("%d%d",&x,&y);
            update(1,n,x,y-w[x],1);
            w[x]=y;
        }
        else if(op[0] == 'Q')
        {
            if(op[1]=='S')
            {
                int x,y; 
                scanf("%d%d",&x,&y);
                printf("%lld\n",find_sum(x,y)); 
            }
            else if(op[1] == 'M')
            {
                int x,y;
                scanf("%d%d",&x,&y);
                printf("%d\n",find_maxx(x,y));
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值