树链剖分练习

洛谷P3384 【模板】轻重链剖分/树链剖分

题目链接:https://www.luogu.com.cn/problem/P3384

大意

给定一个包含N个结点的树,每个节点上包含一个数值,存在一下四种操作:

  • 1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
  • 2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。
  • 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z。
  • 4 x 表示求以 x 为根节点的子树内所有节点值之和

思路

树链剖分出dfs序和时间戳后用线段树维护dfs序(后序操作都在线段树上进行)

1操作维护x到y结点最短路径上节点的值,因为重链上的节点时间戳连续可每个重链进行操作,离开重链只需要在重链头结点往上一个父亲结点即可跳出该重链。当x和y处于同一重链时直接操作后跳出。

2操作同1操作唯一不同为结点加权值操作替换为结点权值相加

3操作因为同一子树结点时间戳连续,从该子树结点数,即可知道该区间范围,那么只需要操作x的时间戳(下面叫做cnt[x])到cnt[x]+siz[x]-1 这一区间值加上z即可(siz[x]为以x为根节点的子树结点数,包含x自身)。

4操作同3操作加上各节点权值即可

另区间加法那必然是要用线段树懒标记的

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lch (k*2)
#define rch (k*2+1)
#define mid ((l+r)/2)
const ll mod=1e9+7;
const ll N=2e5+7;
ll n,m,r,p;
ll c[N],ch[N];
ll e[N],h[N*2],ne[N*2],idx=1,num;
ll tr[N*4],lazy[N*4];
ll dep[N*2],siz[N*2],fa[N*2],son[N*2],tp[N*2],cnt[N*2];
void init(ll k,ll l,ll r)//初始化
{
    if(l>=r)
    {
        tr[k]=ch[l];
        return;
    }
    init(lch,l,mid);
    init(rch,mid+1,r);
    tr[k]=(tr[lch]+tr[rch]+p)%p;
}
void pushdown(ll k,ll l,ll r)//向下传递懒标记
{
    if(!lazy[k])    return;
    lazy[lch]=(lazy[k]+lazy[lch])%p;
    lazy[rch]=(lazy[k]+lazy[rch])%p;
    tr[lch]=(lazy[k]*(mid-l+1)+tr[lch])%p;
    tr[rch]=(lazy[k]*(r-mid)+tr[rch])%p;
    lazy[k]=0;
}
void update(ll k,ll l,ll r,ll ql,ll qr,ll val)//区间加
{
    if(ql<=l&&r<=qr)
    {
        tr[k]=(tr[k]+(r-l+1)*val);
        lazy[k]=(lazy[k]+val);
        return ;
    }
    pushdown(k,l,r);
    if(mid>=ql)
    {
        update(lch,l,mid,ql,qr,val);
    }
    if(mid+1<=qr)
    {
        update(rch,mid+1,r,ql,qr,val);
    }
    tr[k]=(tr[lch]+tr[rch]+p)%p;
}
ll qry(ll k,ll l,ll r,ll ql,ll qr)//区间求和
{
    if(ql<=l&&r<=qr)
    {
        return tr[k];
    }
    pushdown(k,l,r);
    ll ans=0;
    if(mid>=ql)
    {
        ans=(ans+qry(lch,l,mid,ql,qr))%p;
    }
    if(mid+1<=qr)
    {
        ans=(ans+qry(rch,mid+1,r,ql,qr))%p;
    }
    return ans%p;
}
/*          线段树分界线            */
void add(ll a,ll b)//前链星
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll dfs1(ll u,ll f,ll deep)//标记重儿子、轻儿子、结点深度、子树大小
{
    dep[u]=deep;
    fa[u]=f;
    siz[u]=1;
    ll nmax=-1;
    for(ll i=h[u];~i;i=ne[i])
    {
        if(e[i]==f)
            continue;
        siz[u]+=dfs1(e[i],u,deep+1);
        if(siz[e[i]]>nmax)
        {
            nmax=siz[e[i]];
            son[u]=e[i];
        }
    }
    return siz[u];
}
void dfs2(ll u,ll top)//获取时间戳,dfs序,重链头结点
{
    cnt[u]=++num;//时间戳,通过时间戳进行线段树的位置确定
    ch[num]=c[u];//dfs序
    tp[u]=top;
    if(!son[u]) return ;
    dfs2(son[u],top);
    for(ll i=h[u];~i;i=ne[i])
    {
        if(!cnt[e[i]])
            dfs2(e[i],e[i]);
    }
}
void treesum(ll x,ll y)
{
    ll ans=0;
    while(tp[x]!=tp[y])
    {
        if(dep[tp[x]]<dep[tp[y]])
            swap(x,y);
        ans=(ans+qry(1,1,N,cnt[tp[x]],cnt[x]))%p;
        x=fa[tp[x]];
    }
    if(dep[x]>dep[y])
        swap(x,y);
    ans=(ans+qry(1,1,N,cnt[x],cnt[y]))%p;
    cout<<ans<<endl;
}
void treeadd(ll x,ll y,ll val)
{
    while(tp[x]!=tp[y])
    {
        if(dep[tp[x]]<dep[tp[y]])
            swap(x,y);
        update(1,1,N,cnt[tp[x]],cnt[x],val);
        x=fa[tp[x]];
    }
    if(dep[x]>dep[y])
        swap(x,y);
    update(1,1,N,cnt[x],cnt[y],val);
}
int main()
{
    ios::sync_with_stdio(0);
    
    cin>>n>>m>>r>>p;
    memset(h,-1,sizeof(h));
    for(ll i=1;i<=n;i++)
    {
        cin>>c[i];
        c[i]=c[i]%p;
    }
    for(ll i=1;i<n;i++)
    {
        ll x,y;
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    init(1,1,N);
    while(m--)
    {
        ll op,x,y,z;
        cin>>op;
        if(op==1)
        {
            cin>>x>>y>>z;
            treeadd(x,y,z);
        }
        if(op==2)
        {
            cin>>x>>y;
            treesum(x,y);
        }
        if(op==3)
        {
            cin>>x>>z;
            update(1,1,N,cnt[x],cnt[x]+siz[x]-1,z%p);
        }
        if(op==4)
        {
            cin>>x;
            cout<<qry(1,1,N,cnt[x],cnt[x]+siz[x]-1)<<endl;
        }
    }
}

2021杭电第二场多校I love tree

题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=6962

大意

给定一颗n结点的树

现存在两种操作:

1操作将a到b最短路径遍历的第i个结点加上 i 2 i^2 i2;

2操作将x结点的权值输出

思路

很明显的树链剖分+线段树

首先我们假设线段树维护一个数组的话在区间[a,b]内怎样操作。

区间[a,b]内每个结点x存在加上值 ( x − a + 1 ) 2 = ( x − ( a − 1 ) ) 2 (x-a+1)^2=(x-(a-1))^2 (xa+1)2=(x(a1))2;

公式拆分得到 x 2 − 2 ∗ ( a − 1 ) ∗ x + ( a − 1 ) 2 x ^ 2 - 2 * (a-1) * x + (a-1) ^ 2 x22(a1)x+(a1)2

所以我们可以用三颗线段树分别维护每个单项式的系数(当然你也可以一颗线段树维护三个值),最后输出再根据公式输出即可。

同时也存在b大于a时,加上的值为 ( b − x + 1 ) 2 (b-x+1)^2 (bx+1)2

( b − x + 1 ) 2 = ( x − ( b + 1 ) ) 2 (b-x+1)^2=(x-(b+1))^2 (bx+1)2=(x(b+1))2

跳过树链剖分基本操作,我们看看怎么结合线段树(线段树维护的是一个dfs序的数组)

首先我们用lca找到a,b两节点的公共祖先,两节点深度之和-2*祖先深度+1得到a,b两节点最短路径结点数

维护两个数q,p,分别表示左右两边分配到的数(因为我们树链分段后只会从左右两边的数开始分配到线段树)

最后我们只要两个结点不断往上跳的同时判断一下就ok啦!

因为往上跳的同时不可避免的出现逆序的情况,参考上面两个式子,复用一下线段树也就ok了。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lch (k*2)
#define rch (k*2+1)
#define mid ((l+r)/2)
const ll mod=1e9+7;
const ll N=2e5+7;
ll n,m;
ll e[N],h[N*2],ne[N*2],idx=1,num;
ll dep[N*2],siz[N*2],fa[N*2],son[N*2],tp[N*2],cnt[N*2];
struct node//建三棵线段树用结构体减一点代码量
{
    ll tr[N*4];
    void pushdown(ll k)//向下传递懒标记
    {
        if(!tr[k])  return;
        tr[lch]+=tr[k];
        tr[rch]+=tr[k];
        tr[k]=0;
    }
    void update(ll k,ll l,ll r,ll ql,ll qr,ll val)//区间加
    {
        if(ql<=l&&r<=qr)
        {
            tr[k]+=val;
            return ;
        }
        pushdown(k);
        if(ql<=mid)
        {
            update(lch,l,mid,ql,qr,val);
        }
        if(mid+1<=qr)
        {
            update(rch,mid+1,r,ql,qr,val);
        }
        
    }
    ll qry(ll k,ll l,ll r,ll tot)//单点值
    {
        if(l>=r)
        {
            return tr[k];
        }
        pushdown(k);
        if(mid>=tot)
        {
            return qry(lch,l,mid,tot);
        }
        else
        {
            return qry(rch,mid+1,r,tot);
        }
    }
}st[5];
/*          线段树分界线            */
void add(ll a,ll b)//前链星
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
ll dfs1(ll u,ll f,ll deep)//标记重儿子、轻儿子、结点深度、子树大小
{
    dep[u]=deep;
    fa[u]=f;
    siz[u]=1;
    ll nmax=-1;
    for(ll i=h[u];~i;i=ne[i])
    {
        if(e[i]==f)
            continue;
        siz[u]+=dfs1(e[i],u,deep+1);
        if(siz[e[i]]>nmax)
        {
            nmax=siz[e[i]];
            son[u]=e[i];
        }
    }
    return siz[u];
}
void dfs2(ll u,ll top)//获取时间戳,dfs序,重链头结点
{
    cnt[u]=++num;//时间戳,通过时间戳进行线段树的位置确定
    tp[u]=top;
    if(!son[u]) return ;
    dfs2(son[u],top);
    for(ll i=h[u];~i;i=ne[i])
    {
        if(!cnt[e[i]])
            dfs2(e[i],e[i]);
    }
}
ll lca(int x,int y)
{
    while(tp[x]!=tp[y])
    {
        if(dep[tp[x]]<dep[tp[y]])
            swap(x,y);
        x=fa[tp[x]];
    }
    if(dep[x]>dep[y])
    {
        swap(x,y);
    }
    return x;
}
void treeadd(ll x,ll y,ll k)
{
    ll q=1,p=k;
    while(tp[x]!=tp[y])
    {
        if(dep[tp[x]]>dep[tp[y]])
        {
            q+=dep[x]-dep[tp[x]];
            st[1].update(1,1,n,cnt[tp[x]],cnt[x],(q+cnt[tp[x]])*(q+cnt[tp[x]]));
            st[2].update(1,1,n,cnt[tp[x]],cnt[x],(q+cnt[tp[x]]));
            st[3].update(1,1,n,cnt[tp[x]],cnt[x],1);
            x=fa[tp[x]];
            q++;
        }
        else
        {
            p-=dep[y]-dep[tp[y]];
            st[1].update(1,1,n,cnt[tp[y]],cnt[y],(-p+cnt[tp[y]])*(-p+cnt[tp[y]]));
            st[2].update(1,1,n,cnt[tp[y]],cnt[y],(-p+cnt[tp[y]]));
            st[3].update(1,1,n,cnt[tp[y]],cnt[y],1);
            y=fa[tp[y]];
            p--;
        }
    }
    if(dep[x]>dep[y])
    {
        q+=dep[x]-dep[y];
        st[1].update(1,1,n,cnt[y],cnt[x],(q+cnt[y])*(q+cnt[y]));
        st[2].update(1,1,n,cnt[y],cnt[x],(q+cnt[y]));
        st[3].update(1,1,n,cnt[y],cnt[x],1);
    }
    else
    {
        p-=dep[y]-dep[x];
        st[1].update(1,1,n,cnt[x],cnt[y],(-p+cnt[x])*(-p+cnt[x]));
        st[2].update(1,1,n,cnt[x],cnt[y],(-p+cnt[x]));
        st[3].update(1,1,n,cnt[x],cnt[y],1);
    }
}
void treeqry(ll x)
{
    //cout<<st[1].qry(1,1,n,cnt[x])<<"] ["<<st[2].qry(1,1,n,cnt[x])<<"] ["<<st[3].qry(1,1,n,cnt[x])<<"] ["<<cnt[x]<<endl;
    cout<<st[1].qry(1,1,n,cnt[x])-st[2].qry(1,1,n,cnt[x])*2*cnt[x]+st[3].qry(1,1,n,cnt[x])*cnt[x]*cnt[x]<<endl;
}
int main()
{
    ios::sync_with_stdio(0);
    cin>>n;
    memset(h,-1,sizeof(h));
    for(ll i=1;i<n;i++)
    {
        ll x,y;
        cin>>x>>y;
        add(x,y);
        add(y,x);
    }
    dfs1(1,0,1);
    dfs2(1,1);
    cin>>m;
    while(m--)
    {
        int op,x,y;
        cin>>op;
        if(op==1)
        {
            cin>>x>>y;
            int xx=lca(x,y);
            treeadd(x,y,dep[x]+dep[y]-2*dep[xx]+1);
        }
        else
        {
            cin>>x;
            treeqry(x);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第十页

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值