【树剖】Codeforces Round #601 (Div. 1) - D. Tree Queries

题目链接https://codeforces.com/contest/1254/problem/D


题意

给出一棵树,每个点都有权值,初始都为0,有两个操作

  1. 给出 v , d v,d v,d,等概率随机一个点 r r r,对于所有的点 u u u u u u r r r的路径上经过点 v v v。对于所有的这些点 u u u权值加上 d d d
  2. 求点 v v v的权值期望

1 < = n , q < = 150000 1<=n,q<=150000 1<=n,q<=150000


题解

考虑第二个询问点 u u u,计算每个1操作点 v v v的贡献。
如果 l c a ( u , v ) ! = v lca(u,v)!=v lca(u,v)!=v,点 v v v对答案的贡献是 s i z e [ v ] n ∗ d [ v ] \frac{size[v]}{n}*d[v] nsize[v]d[v]
否则,答案的贡献是 n − s i z e [ w ] n ∗ d [ v ] \frac{n-size[w]}{n}*d[v] nnsize[w]d[v],其中 w w w是点 v v v的儿子的子树中包含点 u u u的节点。

对于第一种情况我们只需要记一个 t o t tot tot累加即可,对于第二种情况如果暴力枚举父亲节点肯定会超时。对于这个我们可以采用树剖来进行优化,每一次进行1操作,对于重儿子子树内的点直接区间更新,用线段树或者树状数组维护都可以。等到查询的时候一直往上跳 t o p top top,把没更新过的答案更新一下即可。


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const ll N=4e5+7;
const ll mod=998244353;
int n,q;
ll invn;
struct Edge{
    int v,nxt;
}e[N];
int p[N],edn;
void add(int u,int v){
    e[++edn]=(Edge){v,p[u]};p[u]=edn;
    e[++edn]=(Edge){u,p[v]};p[v]=edn;
}
ll qpow(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;b>>=1;
    }
    return res;
}
int fa[N],le[N],ri[N],son[N],sz[N],top[N],idx;
void dfs(int u){
    sz[u]=1;
    le[u]=++idx;
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa[u]) continue;
        fa[v]=u;
        dfs(v);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
    ri[u]=idx;
}
void bdfs(int u,int k){
    top[u]=k;
    if(son[u]) bdfs(son[u],k);
    for(int i=p[u];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa[u]||v==son[u]) continue;
        bdfs(v,v);
    }
}
ll b[N],c[N];
int lb(int x){return x&(-x);}
void upd(int x,int val){
    while(x<N){
        c[x]=(c[x]+val+mod)%mod;
        x+=lb(x);
    }
}
ll ask(int x){
    ll res=0;
    while(x){
        res=(res+c[x]+mod)%mod;
        x-=lb(x);
    }
    return res;
}
int main(){
    memset(p,-1,sizeof(p));edn=-1;
    scanf("%d%d",&n,&q);
    invn=qpow(n,mod-2);
    for(int i=1,u,v;i<n;i++){
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    dfs(1);
    bdfs(1,1);
    ll tot=0;
    for(int i=1,o,v,d;i<=q;i++){
        scanf("%d",&o);
        if(o==1){
            scanf("%d%d",&v,&d);
            tot=(tot+sz[v]*invn%mod*d%mod);
            b[v]=(b[v]+d)%mod;
            if(son[v]){
                ll val=-sz[v]*invn%mod*d%mod+(n-sz[son[v]])*invn%mod*d%mod;
                upd(le[son[v]],val);
                upd(ri[son[v]]+1,-val);
            }
        }
        else{
            scanf("%d",&v);
            ll ans=(tot-sz[v]*invn%mod*b[v]%mod+mod)%mod+b[v];
            ans=(ans+ask(le[v]))%mod;
            while(1){
                v=top[v];
                if(v==1) break;
                ans=(ans-sz[fa[v]]*invn%mod*b[fa[v]]%mod+(n-sz[v])*invn%mod*b[fa[v]]%mod+mod)%mod;
                v=fa[v];
            }
            printf("%lld\n",ans);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值