题解 三只企鹅 树链剖分

题解 三只企鹅

题目描述

在这里插入图片描述
数据范围: n , m , w ≤ 1 0 5 , u , v ≤ n n,m,w \leq 10^5,u,v \leq n n,m,w105,u,vn

具体做法与心路历程

考场上的错误方法不多说。

具体做法

我们把查询操作写出来:
a n s = ∑ v ∈ M o d i f y d i s u + d i s v − 2 d i s l c a u , v ans=\sum_{v \in Modify}{dis_u+dis_v-2dis_{lca_{u,v}}} ans=vModifydisu+disv2dislcau,v
我们发现前面两个 d i s dis dis比较好求。重点是怎么求 d i s l c a u , v dis_{lca_{u,v}} dislcau,v

因为直接修改其他所有点的距离没有优秀的方法,所以我们试着把修改改成差分

对于 u , v u,v u,v,如果他们在 r o o t root root的不同子树中,那么他们的 d i s l c a u , v = 0 dis_{lca_{u,v}}=0 dislcau,v=0,不用考虑。

也就是说,对于一个修改操作 v v v,他能影响到的点只在他所在的到根的子树中。

我们把 v v v到根的路径上的每个点都打一个标记,记 u u u的标记个数为 c n t u cnt_u cntu,考虑查询时求出 u u u的每个祖先作为 l c a lca lca的个数再来计算。

那么有如下计算方式:

  1. d i s f u × ( c n t f u − c n t u ) dis_{f_u} \times (cnt_{f_u}-cnt_u) disfu×(cntfucntu)
  2. u = f u u=f_u u=fu
  3. 重复1,2,直到 u u u为根

将以上拆开后把 c n t u cnt_u cntu放在一起有: c n t u × ( d i s u − d i s f u ) cnt_u \times (dis_u - dis_{f_u}) cntu×(disudisfu)。那么这个查询就变成查询 u u u到根上这些所有的和了。

我们可以轻易维护好 c n t u cnt_u cntu(树链剖分),那么我们用线段树维护每个点 u u u d i s u − d i s f u dis_{u}-dis_{f_u} disudisfu的倍数,最后区间求和即可。(意思就是区间加时对于每个点加的值不一样)

如果不清楚可以看代码。

C o d e \mathcal{Code} Code

/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019年10月28日 星期一 20时55分56秒
*******************************/
#include<cstdio>
#include<algorithm>
#define int long long

using namespace std;

struct IO{
    template<typename T>
    IO & operator>>(T&res)
    {
        T q=1;char ch;
        while((ch=getchar())<'0' or ch>'9')if(ch=='-')q=-q;
        res=(ch^48);
        while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48);
        res*=q;
        return *this;
    }
}cin;

struct edge{
    int to,next,w;
    edge(int a=0,int b=0,int c=0):to(a),next(b),w(c){}
};

const int maxn=2e5+10;
int n,m,head[maxn],cnt,f[maxn],top[maxn],size[maxn],seg[maxn],son[maxn],dis[maxn],tot,rev[maxn];
long long res;
edge e[maxn<<1];

void add(int u,int v,int w)
{
    e[++cnt]=edge(v,head[u],w);
    head[u]=cnt;
}

void dfs1(int now,int fa)
{
    f[now]=fa; size[now]=1;
    for(int i=head[now];i;i=e[i].next)
        if(e[i].to!=fa)
        {
            dis[e[i].to]=dis[now]+e[i].w;
            dfs1(e[i].to,now);
            size[now]+=size[e[i].to];
            if(size[son[now]]<size[e[i].to])
                son[now]=e[i].to;
        }
}

void dfs2(int now,int tp)
{
    seg[now]=++seg[0];
    rev[seg[0]]=now;
    top[now]=tp;
    if(son[now])
        dfs2(son[now],tp);
    for(int i=head[now];i;i=e[i].next)
        if(e[i].to!=f[now] && e[i].to!=son[now])
            dfs2(e[i].to,e[i].to);
}

/*{{{线段树*/

namespace SegmentTree{

    long long sum[maxn*4],lazy[maxn*4],delta[maxn*4];


    void update(int k)
    {
        sum[k]=sum[k<<1]+sum[k<<1|1];
    }

    void work(int k,long long val)
    {
        sum[k]+=delta[k]*val;
        lazy[k]+=val;
    }

    void pushdown(int k)
    {
        if(!lazy[k]) return;
        work(k<<1,lazy[k]);
        work(k<<1|1,lazy[k]);
        lazy[k]=0;
    }

    void build(int k,int l,int r)
    {
        if(l==r)
        {
            delta[k]=dis[rev[l]]-dis[f[rev[l]]];
            return;
        }
        int mid=(l+r)>>1;
        build(k<<1,l,mid);
        build(k<<1|1,mid+1,r);
        delta[k]=delta[k<<1]+delta[k<<1|1];
    }

    void modify(int k,int l,int r,int x,int y,int val)
    {
        if(l>=x && r<=y) return work(k,val);
        if(l>y  ||  r<x) return;
        int mid=(l+r)>>1;
        pushdown(k);
        modify(k<<1,l,mid,x,y,val); modify(k<<1|1,mid+1,r,x,y,val);
        return update(k);
    }

    long long query(int k,int l,int r,int x,int y)
    {
        if(l>=x && r<=y) return sum[k];
        if(l>y  ||  r<x) return 0;
        int mid=(l+r)>>1;
        pushdown(k);
        return query(k<<1,l,mid,x,y)+query(k<<1|1,mid+1,r,x,y);
    }

};

/*}}}*/

void modify(int now)
{
    ++tot; res+=dis[now];
    while(top[now])
    {
        SegmentTree::modify(1,1,seg[0],seg[top[now]],seg[now],1);
        now=f[top[now]];
    }
}

long long solve(int now)
{
    long long res=::res+tot*dis[now];
    while(top[now])
    {
        res-=2LL * SegmentTree::query(1,1,seg[0],seg[top[now]],seg[now]);
        now=f[top[now]];
    }
    return res;
}


signed main()
{
    /*freopen("express.in","r",stdin);*/
    /*freopen("express.out","w",stdout);*/
    cin>>n>>m;
    int u,v,w;
    for(int i=1;i<n;i++)
    {
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    dfs1(1,0); dfs2(1,1);
    SegmentTree::build(1,1,seg[0]);
    while(m--)
    {
        int opt,u;
        cin>>opt>>u;
        if(opt==1)
            modify(u);
        else
            printf("%lld\n",solve(u));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值