bzoj 4390(树上差分)

11 篇文章 0 订阅

传送门

题意:

给你一颗有 n n n个结点的树以及 m m m个路径。对于每一个路径 p a t h i path_i pathi,代表着你将会从 u i u_i ui走到 v i v_i vi。现在问你,你走完着 m m m个路径后,在这 n n n个结点中经过的最多的次数。

题目分析:

首先,如果我们用 d f s dfs dfs在树上暴力去跑的话,显然时间肯定是接受不了的。因此我们需要考虑一种较为优美的算法。

我们发现,经过每一个路径 p a t h i path_i pathi,本质上是使路径 p a t h i path_i pathi上的所有的点权 + 1 +1 +1,如果这个问题是在序列上的话,我们直接就可以用线段树或差分树状数组去做。而正因为这个问题是在树上进行的,因此为了简化问题,我们则需要将树形的结构转化成链式结构。

因此根据转化的不同,该问题可以通过树链剖分或者树上差分去解决。

而显然树上差分相对来说比较简单。

我们只需要令 c n t [ u i ] + 1 cnt[u_i]+1 cnt[ui]+1 c n t [ v i ] + 1 cnt[v_i]+1 cnt[vi]+1,并且令 c n t [   l c a [ u i , v i ]   ] − 1 cnt[~lca[u_i,v_i]~]-1 cnt[ lca[ui,vi] ]1 c n t [   f a [   l c a [ u i , v i ]   ]   ] − 1 cnt[~fa[~lca[u_i,v_i]~] ~]-1 cnt[ fa[ lca[ui,vi] ] ]1即可。如果我们采用上述差分方法,在最后我们进行 d f s dfs dfs回溯的时候,当前结点 u u u就能够获得他的儿子们的权值。

整体的时间复杂度: O ( n l o g n ) \mathcal{O}(nlogn) O(nlogn)

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=500005;
const int LOG=20;
struct Node{
    int to,next;
}q[maxn<<1];
int head[maxn],cnt=0;
void add_edge(int from,int to){
    q[cnt].to=to;
    q[cnt].next=head[from];
    head[from]=cnt++;
}
struct LCA{//倍增lca
    int anc[maxn][LOG],depth[maxn];
    void dfs(int x,int fa,int dis){
        anc[x][0]=fa;depth[x]=dis;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(to==fa) continue;
            dfs(to,x,dis+1);
        }
    }
    void init(int root,int n){
        dfs(root,-1,1);
        for(int j=1;j<LOG;j++){
            for(int i=1;i<=n;i++){
                anc[i][j]=anc[ anc[i][j-1] ][j-1];
            }
        }
    }
    void swim(int &x,int h){
        for(int i=0;h>0;i++){
            if(h&1)
                x=anc[x][i];
            h>>=1;
        }
    }
    int query(int x,int y){
        if(depth[x]<depth[y]) swap(x,y);
        swim(x,depth[x]-depth[y]);
        if(x==y) return x;
        for(int i=LOG-1;i>=0;i--){
            if(anc[x][i]!=anc[y][i]){
                x=anc[x][i];
                y=anc[y][i];
            }
        }
        return anc[x][0];
    }
}lca;
int Bit[maxn];
void Search(int x,int fa){//最优一次dfs获取权值
    for(int i=head[x];i!=-1;i=q[i].next){
        int to=q[i].to;
        if(to==fa) continue;
        Search(to,x);
        Bit[x]+=Bit[to];
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    cnt=0;
    for(int i=1;i<n;i++){
        int from,to;
        scanf("%d%d",&from,&to);
        add_edge(from,to);
        add_edge(to,from);
    }
    lca.init(1,n);
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        Bit[l]++,Bit[r]++;//差分
        int x=lca.query(l,r);
        Bit[x]--;
        Bit[lca.anc[x][0]]--;
    }
    Search(1,-1);
    int maxx=0;
    for(int i=1;i<=n;i++){
        maxx=max(maxx,Bit[i]);
    }
    printf("%d\n",maxx);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值