bzoj3924(点分树

bzoj3924(点分树

题意:

1.树上找点 u u u使得 ∑ d i s ( u , v ) ∗ u \sum dis(u,v)*u dis(u,v)u最小

2.单点修改点权

思路:
考虑 u u u为当前决策点,对于其儿子 v v v,移向其儿子的权值改变是 ( − s u m [ v ] ∗ e d g e [ i ] + ( s u m [ u ] − s u m [ v ] ) ∗ e d g e [ i ] ) (-sum[v]*edge[i]+(sum[u]-sum[v])*edge[i]) (sum[v]edge[i]+(sum[u]sum[v])edge[i])

改变小于0的时候, s u m [ u ] < 2 ∗ s u m [ v ] sum[u]<2*sum[v] sum[u]<2sum[v],显然这样的点只有一个,如果没有修改点权这样我们得到一个 O ( n 2 ) O(n^2) O(n2)的算法

但是我们发现局限于深度,再加上带修,所以建出点分树

深度 l o g n logn logn层,从点分树根出发,对于变小的儿子,直接跳到相应的分治重心跳过去,考虑修改与查询,每个点维护自己对自己的答案,自己对父亲的答案,查询的时候就是在点分树上,加上自己对自己的,减去自己对父亲的,加上父亲对自己的,但同时我们也得加上那条边的贡献,所以还得维护多一个点数的贡献,修改也很简单

注意这个算法的复杂度是 O ( 20 ∗ n l o g n ) O(20*nlogn) O(20nlogn),取决于每个点的出度

#include<bits/stdc++.h>
#define int long long 
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
int head1[maxn],head2[maxn],next1[maxn<<1],next2[maxn<<1],ver1[maxn<<1],ver2[maxn<<1],edge1[maxn<<1],edge2[maxn<<1];
int tot1,tot2,sz[maxn],mxson[maxn],S,rt,dfn[maxn],d[maxn],st[maxn<<1][21],lg[maxn<<1],ti,fa[maxn],n,q;
ll sumd[maxn],sumf[maxn],sum[maxn];//点分树上自己对自己的答案贡献,自己对父亲的答案贡献,自己对自己的点数贡献
ll dep[maxn];
bool v[maxn];
void add1(int x,int y,int z){
    ver1[++tot1]=y,next1[tot1]=head1[x],edge1[tot1]=z,head1[x]=tot1;
}
void add2(int x,int y,int z){
    ver2[++tot2]=y,next2[tot2]=head2[x],edge2[tot2]=z,head2[x]=tot2;
}
void dfs(int x,int f,int dd){
    dfn[x]=++ti;d[x]=d[f]+1;st[ti][0]=x;
    dep[x]=dd;
    for(int i=head1[x];i;i=next1[i]){
        int y=ver1[i];
        if(y==f)continue;
        dfs(y,x,dd+edge1[i]);   
        st[++ti][0]=x;
    }
}
void RMQ(){
    lg[0]=-1;
    for(int i=1;i<=ti;++i)lg[i]=lg[i>>1]+1;
    for(int j=1;j<=20;++j)
        for(int i=1;(i+(1<<j)-1)<=ti;++i){
            int r=i+(1<<(j-1));
            st[i][j]=d[st[i][j-1]]<d[st[r][j-1]]?st[i][j-1]:st[r][j-1];
        }
}
int LCA(int l,int r){
    if(l>r)swap(l,r);
    int k=lg[r-l+1];
    return d[st[l][k]]<d[st[r-(1<<k)+1][k]]?st[l][k]:st[r-(1<<k)+1][k];
}
void getRoot(int x,int f){
    sz[x]=1;mxson[x]=0;
    for(int i=head1[x];i;i=next1[i]){
        int y=ver1[i];
        if(v[y]||y==f)continue;
        getRoot(y,x);
        sz[x]+=sz[y];
        mxson[x]=max(mxson[x],sz[y]);
    }
    mxson[x]=max(mxson[x],S-sz[x]);
    if(mxson[x]<mxson[rt])rt=x;
}
void init(int x){
    S=sz[x];
    mxson[rt=0]=maxn;
    getRoot(x,0);
}
void dfz(int x){
    v[x]=1;
    for(int i=head1[x];i;i=next1[i]){
        int y=ver1[i];
        if(v[y])continue;
        init(y);
        add2(x,rt,y);
        fa[rt]=x;
        dfz(rt);   
    }
}
int dis(int x,int y){
    return dep[x]+dep[y]-2*dep[LCA(dfn[x],dfn[y])];
}
void update(int x,int val){
    sum[x]+=val;
    for(int i=x;fa[i];i=fa[i]){
        int len=dis(x,fa[i]);
        sum[fa[i]]+=val;
        sumd[fa[i]]+=1ll*val*len;
        sumf[i]+=1ll*val*len;
    }
}
ll cal(int x){
    // dbg(x,sumd[x]);
    ll ans=sumd[x];
    for(int i=x;fa[i];i=fa[i]){
        int len=dis(x,fa[i]);
        ans+=1ll*(sum[fa[i]]-sum[i])*len;
        ans+=(sumd[fa[i]]-sumf[i]);
    }
    return ans;
}
ll query(int x){
    ll ans=cal(x);
    for(int i=head2[x];i;i=next2[i]){
        int y=ver2[i];
        if(cal(edge2[i])<ans)return query(y);
    }   
    return ans;
}
signed main(){
    scanf("%lld%lld",&n,&q);
    // dbg(1);
    int u,v,w;
    for(int i=1;i<n;++i){
        scanf("%lld%lld%lld",&u,&v,&w);
        add1(u,v,w);add1(v,u,w);
        // dbg(head1,ver1,next1,tot1);
    }
    dfs(1,0,0);
    RMQ();
    S=n;
    mxson[rt=0]=maxn;
    getRoot(1,0);
    int root=rt;
    dfz(rt);
    for(int i=1;i<=q;++i){
        scanf("%lld%lld",&u,&w);
        update(u,w);
        cout<<(query(root))<<"\n";
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值