J.砍树【蓝桥杯】树上差分+LCA

树上差分

多次对树上的一些路径做加法操作,然后询问某个点或某条边经过操作后的值,就要考虑树上差分了。

点差分

在这里插入图片描述
模拟这个过程
在这里插入图片描述
对x到y路径上的点权值均+1,可以等价成对x和y的权值加1,对lca的权值-1,对fa[lca]的权值-1

  • 遍历到x,权值为1
  • 回溯到4,通过递归求得子树和,得到权值为1,
  • 遍历到7,再回溯回4,权值不变
  • 回溯到3,权值-1+1为0
  • 遍历到5,再遍历到y,y权值为1
  • 回溯到5,权值+1为1
  • 回溯到3,权值为0+1=1
  • 再回溯到2,权值为-1+1=0

故做到了通过一趟dfs就可以求出权值,且不影响其他无关的点

边差分

在这里插入图片描述
在这里插入图片描述
这个模拟的过程和点差分差不多,不过这里边的权值是映射到边下面那个点上,如这个操作是

  • x的权值+1,即4到6这条边的权值+1
  • y的权值+1,即5到8这条边的权值+1
  • lca的权值-2

砍树【蓝桥杯】例题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:题目说要砍掉边,其实就是这条边是这些路径的公共边,且走了m次,用边差分,cnt[i]表示编号为i的边走过多少次,即权值,e[i]表示标号为i的点对应的边

#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
vector<pair<int,int>> v[100005];
LL cnt[100005],f[100005][21],dep[100005],e[100005];
void dfs(int u,int fa)
{
    dep[u]=dep[fa]+1;
    f[u][0]=fa;
    for(int i=1;(1<<i)<=dep[u];i++) f[u][i]=f[f[u][i-1]][i-1];
    for(auto &p:v[u])
    {
        if(p.first==fa) continue;
        dfs(p.first,u);
        //给点赋值对应的边
        e[p.first]=p.second;
    }
}
//求LCA
int lca(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=20;i>=0;i--)
    {
        if(dep[f[u][i]]>=dep[v]) u=f[u][i];
        if(u==v) return u;
    }
    for(int i=20;i>=0;i--)
    {
        if(f[u][i]!=f[v][i]) 
        {
            u=f[u][i];
            v=f[v][i];
        }
    }
    return f[u][0];
}
void dfs2(int u)
{
    for(auto &p:v[u])
    {
        if(p.first==f[u][0]) continue;
        dfs2(p.first);
        cnt[e[u]]+=cnt[e[p.first]];
    }
}
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        //记录边及编号
        v[a].push_back({b,i});
        v[b].push_back({a,i});
    }
    dfs(1,0);
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        //树上差分边差分操作
        cnt[e[a]]++;
        cnt[e[b]]++;
        cnt[e[lca(a,b)]]-=2;
    }
    //遍历回溯
    dfs2(1);
    int ans=0;
    for(int i=1;i<n;i++)
    {
        if(cnt[i]==m) ans=i;
    }
    cout<<ans<<endl;
    return 0;
}
  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值