题解【P3833】[SHOI2012]魔法树

看原题戳这儿

如题,肯定是树链剖分的题。

零、写在前面

建议先A掉这道模板题(不会的先看这个

前置知识:链式前向星,树,dfs序,LCA,树形dp,线段树,树链剖分(······)

一定要先完全学懂(!!!)

一、审题

将题面简化一下就是:

已知一棵包含 (N(0 le N le 100000)) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

操作 1: 格式: $A u v d $ 表示将树从 (u)(v) 结点最短路径上所有节点的值都加上 (d)

操作 2: 格式:(Q u) 表示求以 (u) 为根节点的子树内所有节点值之和

我们已经知道树链剖分有四种功能:

  • 处理任意两点间路径上的点权和

  • 处理一点及其子树的点权和

  • 修改任意两点间路径上的点权

  • 修改一点及其子树的点权

很明显,这是一道树链剖分的板子题。(那为什么还是省选题?)

二、预处理

在这里预处理时,我们需要用两个(dfs())

为什么不能合并?因为(dfs2())要用(dfs1())推出

dfs1()

这个dfs要处理这四件事情:

  1. 标记每个点的深度dep[ ]

  2. 标记每个点的父亲fa[ ]

  3. 标记每个非叶子节点的子树大小(含它自己)

  4. 标记每个非叶子节点的重儿子编号son[ ]

inline void dfs1(int u,int f,int deep)//x表示当前节点,f表示当前节点的父亲,deep表示当前节点的深度
{
    dep[u]=deep;//处理深度
    fa[u]=f;//处理父亲
    siz[u]=1;//先标记
    int maxson=-1;//最大的儿子
    for(int i=head[u];i!=0;i=e[i].nxt)
	{
        int v=e[i].to;
        if(v==f)continue;
        dfs1(v,u,deep+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson) son[u]=v,maxson=siz[v];//判断是否是重儿子
    }
}

dfs2()

这个dfs要处理这四件事情:

  1. 标记每个点的新编号

  2. 赋值每个点的初始值到新编号上

  3. 处理每个点所在链的顶端

  4. 处理每条链

顺序:先处理重儿子再处理轻儿子

inline void dfs2(int x,int topf)//x表示当前节点,topf当前节点所在链的最顶端的节点 
{
    id[x]=++cnt;//重新编号
    wt[cnt]=w[x];//重新赋值
    top[x]=topf;//处理链的顶端
    if(!son[x]) return;//如果没有儿子就返回
    dfs2(son[x],topf);//先处理重儿子
    for(int i=head[x];i!=0;i=e[i].nxt)
    {
    	int v=e[i].to;
        if(v==fa[x]||v==son[x])continue;//判定是否重复
        dfs2(v,v);//每一个轻儿子都有一条以他为起始的链
    }
}

(One Problem)

为什么要先处理重儿子?

答:这样在后面我们所要处理的所有区间中点的编号(新编号) 均为连续的 ,这样方便我们建线段树。

三、建树

还不会线段树的戳这儿

上文说到要建线段树,那么按题意建树就可以了。

不过需要注意的是,建树这一步要放在处理问题之前,不然(······)

四、树剖

1、处理任意两点间路径时: 设所在链顶端的深度更深的那个点为x点

方法:

  • ans加上x点到x所在链顶端 这一段区间的点权和

  • 把x跳到x所在链顶端的那个点的上面一个点

不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可

这时我们注意到,我们所要处理的所有区间均为连续编号(新编号) ,于是想到线段树,用线段树处理连续编号区间和(为什么代码这么长?就因为这儿)

每次查询时间复杂度为(O(log^2n)),不错。

inline int qRange(int x,int y)
{
    int ans=0;
    while(top[x]!=top[y])//若两个点不在一条链上
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);//把链的顶端更深的放在前面
        res=0;
        query(1,1,a,id[top[x]],id[x]);//使用线段树将ans加上x点到x所在链顶端,这一段区间的点权和
        ans+=res;
        x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点,继续循环
    }
    //两个点终于在一条链上了
    if(dep[x]>dep[y]) swap(x,y);//把链的顶端更深的放在前面
    res=0;
    query(1,1,a,id[x],id[y]);//再求一次
    ans+=res;
    return ans;
}

2、处理一点及其子树的点权和:

想到要记录每个非叶子节点的子树大小(含它自己),并且每个子树的新编号都是连续的,然后直接线段树区间查询就行啦!!!时间复杂度为$ O(logn) $,很好

inline int qSon(int x)
{
    res=0;
    query(1,1,a,id[x],id[x]+siz[x]-1);//子树区间右端点id[x]+siz[x]-1 
    return res;
}

当然,区间修改是和区间查询一样的

五、代码

inline void updRange(int x,int y,int k)
{ 
    k%=mod;
    while(top[x]!=top[y])
	{
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,a,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,a,id[x],id[y],k);
}

inline void updSon(int x,int k)
{
    update(1,1,a,id[x],id[x]+siz[x]-1,k);
}
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
struct node{
	long long int to,nxt;
} e[500001];
long long int a,b,r,mod,res;
long long int ecnt,head[100001],w[100001],wt[100001];
long long int tree[2000001],laz[2000001];
long long int son[100001],id[100001],fa[100001],cnt,dep[100001],siz[100001],top[100001]; 
inline void add(long long int x,long long int y)
{
    e[++ecnt].to=y;
    e[ecnt].nxt=head[x];
    head[x]=ecnt;
}
//--------------------------------------
inline void pushdown(long long int p,long long int lenn)
{
    laz[p<<1]+=laz[p];
    laz[p<<1|1]+=laz[p];
    tree[p<<1]+=laz[p]*(lenn-(lenn>>1));
    tree[p<<1|1]+=laz[p]*(lenn>>1);
    tree[p<<1]%=mod;
    tree[p<<1|1]%=mod;
    laz[p]=0;
}
inline void build(long long int p,long long int l,long long int r)
{
	long long int mid=(l+r)>>1;
    if(l==r)
	{
        tree[p]=wt[l];
        return;
    }
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    tree[p]=tree[p<<1]+tree[p<<1|1];
}
inline void query(long long int p,long long int l,long long int r,long long int L,long long int R)
{
	long long int mid=(l+r)>>1;
    if(L<=l&&r<=R)
	{
		res+=tree[p];
		return;
	}
    else
	{
        if(laz[p]) pushdown(p,r-l+1);
        if(L<=mid) query(p<<1,l,mid,L,R);
        if(R>mid) query(p<<1|1,mid+1,r,L,R);
    }
}
inline void update(long long int p,long long int l,long long int r,long long int L,long long int R,long long int k)
{
	long long int mid=(l+r)>>1;
    if(L<=l&&r<=R)
	{
        laz[p]+=k;
        tree[p]+=k*(r-l+1);
    }
    else
	{
        if(laz[p]) pushdown(p,r-l+1);
        if(L<=mid) update(p<<1,l,mid,L,R,k);
        if(R>mid) update(p<<1|1,mid+1,r,L,R,k);
        tree[p]=tree[p<<1]+tree[p<<1|1];
    }
}
//---------------------------------
inline void updRange(long long int x,long long int y,long long int k)
{ 
    while(top[x]!=top[y])
	{
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        update(1,1,a,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    update(1,1,a,id[x],id[y],k);
}
inline long long int qSon(long long int x)
{
    res=0;
    query(1,1,a,id[x],id[x]+siz[x]-1);
    return res;
}
inline void dfs1(long long int u,long long int f,long long int deep)
{
    dep[u]=deep;
    fa[u]=f;
    siz[u]=1;
    long long int maxson=-1;
    for(long long int i=head[u];i!=0;i=e[i].nxt)
	{
        long long int v=e[i].to;
        if(v==f)continue;
        dfs1(v,u,deep+1);
        siz[u]+=siz[v];
        if(siz[v]>maxson) son[u]=v,maxson=siz[v];
    }
}
inline void dfs2(long long int x,long long int topf)
{
    id[x]=++cnt,wt[cnt]=w[x],top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(long long int i=head[x];i!=0;i=e[i].nxt)
	{
        long long int v=e[i].to;
        if(v==fa[x]||v==son[x]) continue;
        dfs2(v,v);
    }
}
int main()
{
    scanf("%lld",&a);r=0;
    for(long long int i=1;i<a;++i)
	{
        long long int a,b;
        scanf("%lld%lld",&a,&b);
        add(a,b);add(b,a);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,a);
    while(1)
	{
        char k;
		long long int x,y,z;
		cin>>k;
        if(k=='A')
		{
            scanf("%lld%lld%lld",&x,&y,&z);
            updRange(x,y,z);
        }
        else
		{
            scanf("%lld",&x);
            printf("%lld
",qSon(x));
        }
    }
    return 0;
}

完美撒花!!!

原文地址:https://www.cnblogs.com/zhnzh/p/13393508.html