树链剖分代码细解

总结回顾类文章,酌情观看。


Shape Of You
头图

Linux找图太难了呜呜
image

The club isn't the best place to find a lover
So the bar is where I go
Me and my friends at the table doing shots
Drinking faster and then we talk slow
You come over and start up a conversation with just me
And trust me I'll give it a chance now
Take my hand, stop, Put Van The Man on the jukebox
And then we start to dance, and now I'm singing like
Girl, you know I want your love
Your love was handmade for somebody like me
Come on now, follow my lead
I may be crazy, don't mind me, say
Boy, let's not talk too much
Grab on my waist and put that body on me
Come on now, follow my lead
Come—come on now, follow my lead
I'm in love with the shape of you
We push and pull like a magnet do
Although my heart is falling too
I'm in love with your body
And last night you were in my room
And now my bedsheets smell like you
Every day discovering something brand new
I'm in love with your body

一些

HAOI2015 树上操作 代码拆分

	void Wadd(int u,int v)
    {
        to[++cnt]=v;
        ne[cnt]=hh[u];
        hh[u]=cnt;
    }

标准的链式前向星连边。

    void Wpushup(int rt)
    {
        tsum[rt]=tsum[ls]+tsum[rs];
    }

推上(更新)操作,进行一些更改后会随之变动的数据,如和与最大值。

    void Wpushdown(int rt,int lenn)
    {
        tsum[ls]+=laz[rt]*(lenn-(lenn>>1));
        tsum[rs]+=laz[rt]*(lenn>>1);
        laz[ls]+=laz[rt],laz[rs]+=laz[rt];
        laz[rt]=0;
    }

推下(下放)操作,进行一些需要先处理的更改,主要为 \(lazy\) 标记的下放。

    void Wbuild(int rt,int l,int r)
    {
        if(l==r)
        {
            tsum[rt]=wt[l];
            return;
        }
        Wbuild(ls,l,mid);
        Wbuild(rs,mid+1,r);
        Wpushup(rt);
    }

建树。

    void Wadp(int rt,int l,int r,int x,ll v)
    {
        if(l==r)
        {
            tsum[rt]+=v;
            return;
        }
        if(laz[rt])
            Wpushdown(rt,r-l+1);
        if(x<=mid)
            Wadp(ls,l,mid,x,v);
        else
            Wadp(rs,mid+1,r,x,v);
        Wpushup(rt);
    }

单点更改。\(l\)\(r\) 为当前节点的左右边界,\(x\) 为修改的点编号,\(v\) 为更改后 / 增减的值。有 \(lazy\) 标记先下放,下放后随之更新。(不更新会WA)

    void Wadi(int rt,int l,int r,int x,int y,ll v)
    {
        if(x<=l&&r<=y)
        {
            laz[rt]+=v;
            tsum[rt]+=v*(r-l+1);
            return ;
        }
        if(laz[rt])
            Wpushdown(rt,r-l+1);
        if(x<=mid)
            Wadi(ls,l,mid,x,y,v);
        if(y>mid)
            Wadi(rs,mid+1,r,x,y,v);
        Wpushup(rt);
    }

整条链修改(区间修改)。\(x\)\(y\)分别为修改的左右边界,操作基本同上,加值时用 \(lazy\) 标记。

    ll Wqsum(int rt,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
            return tsum[rt];
        if(laz[rt])
            Wpushdown(rt,r-l+1);
        ll ans=0;
        if(x<=mid)
            ans+=Wqsum(ls,l,mid,x,y);
        if(y>mid)
            ans+=Wqsum(rs,mid+1,r,x,y);
        return ans;
    }

区间查询和。可以自己画个草图模拟一下,只要左右区间中有在所求范围内的就递归下去。

关于为什么这里pushdown后不用pushup

我个人的想法是:做修改操作时,已经将当前的 \(sum\) 更新过了,即使 \(lazy\) 没有下放,导致的结果只会是当前 \(sum\) 仍是正确的,但子区间答案是未更新的,所以不会影响结果正确性。

有不同想法也可以评论留言。

    ll Wquesum(int x,int y)
    {
        ll ans=0;
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            ans+=Wqsum(1,1,n,id[top[x]],id[x]);
            x=fa[top[x]];
        }
        if(dep[x]<dep[y])
            swap(x,y);
        ans+=Wqsum(1,1,n,id[y],id[x]);
        return ans;
    }

求两个节点最短路径上权值和。利用已经求出的 LCA 相关信息不断向上取当前两点较深的那个至链首的区间和,然后跳到链首的父节点,重复上述操作,直到两点在同一条链上,一个区间和收尾。

    void Wdfs1(int u,int f,int deep)
    {
        dep[u]=deep,fa[u]=f,siz[u]=1;
        for(int i=hh[u];i!=-1;i=ne[i])
        {
            int v=to[i];
            if(v==f)
                continue;
            Wdfs1(v,u,deep+1);
            siz[u]+=siz[v];
            if(siz[v]>siz[son[u]])
                son[u]=v;
        }
    }

dfs1 函数,负责求出点的深度,父节点,子树大小,以及重子节点编号,递归形式还是比较好理解的。

    void Wdfs2(int u,int topf)
    {
        id[u]=++tot,top[u]=topf;
        wt[tot]=w[u];
        if(!son[u])
            return;
        Wdfs2(son[u],topf);
        for(int i=hh[u];i!=-1;i=ne[i])
        {
            int v=to[i];
            if(v==fa[u]||v==son[u])
                continue;
            Wdfs2(v,v);
        }
    }

dfs2 函数,负责求出每一个点的新的编号,新编号下的权值,以及所在链的链首。先遍历重子节点,在遍历轻子节点,轻子节点所在一条新的链,自己为链首。

    short main()
	{
		memset(hh,-1,sizeof hh);
        n=qr,m=qr;
        fo(i,1,n)
            w[i]=qr;
        fo(i,1,n-1)
        {
            int a=qr,b=qr;
            Wadd(a,b),Wadd(b,a);
        }
        Wdfs1(1,0,1),Wdfs2(1,1);
        Wbuild(1,1,n);
        while(m--)
        {
            int op=qr,x=qr;
            if(op==1)
            {
                int y=qr;
                Wadp(1,1,n,id[x],y);
            }
            else if(op==2)
            {
                int y=qr;
                Wadi(1,1,n,id[x],id[x]+siz[x]-1,y);
            }
            else
                printf("%lld\n",Wquesum(1,x));
        }
		return Ratio;
	}

主函数。\(head\) 数组初始化,输入,连边。两次 dfs 初始化,建树,然后进行所需的操作。


完结撒花~

image

posted @ 2024-05-17 15:23  DrRatio  阅读(35)  评论(4编辑  收藏  举报