最近公共祖先之:【树上差分:把从x到y的路径上加上C】【一个有树边又有非树边的图:把它变成不连通的】【树边+非树边会形成环】【斩断联通性】

本文探讨了由非树边与树边构成的图的性质,并通过C++实现了一个基于LCA(最近公共祖先)的算法。该算法在O(1)时间内进行树上差分,进而快速求解每棵子树的和。通过预处理倍增数组,可以高效地处理大规模图结构中的非树边。算法在解决树上差分问题时表现出高效性能。

一.非树边与树边构成图的性质

 二.本题的题解

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=N*2;
int n,m;
int e[M],ne[M],h[N],idx;
int depth[N];
int fa[N][17]; //17是这样算的:点数是100000,log2(100000)大概是17
int q[N];
int d[N]; //存每一个点差分的一个值

int ans; //求的是每一棵子树的总和是多少

void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}

void bfs()
{
    memset(depth,0x3f,sizeof depth);
    depth[0]=0;
    depth[1]=1;
    int hh=0,tt=-1;
    q[++tt]=1;
    
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(depth[j]>depth[t]+1)
            {
                depth[j]=depth[t]+1;
                q[++tt]=j;
                fa[j][0]=t;
                
                for(int k=1;k<=16;k++)
                {
                    fa[j][k]=fa[fa[j][k-1]][k-1];
                }
            }
        }
    }
    
}

int lca(int a,int b)
{
    if(depth[a]<depth[b])
    {
        swap(a,b);
    }
    
    for(int k=16;k>=0;k--)
    {
        if(depth[fa[a][k]]>=depth[b])
        {
            a=fa[a][k];
        }
    }
    
    if(a==b) return a;
    
    for(int k=16;k>=0;k--)
    {
        if(fa[a][k]!=fa[b][k])
        {
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    
    return fa[a][0];
}

//树上差分后求类似于前缀和
int dfs(int u,int father)
{
    int res=d[u];
    /*
    如果在一维差分中,就类似于求
    d[]数组的前缀和数组,
    枚举到u时,就要加上d[u];
    但是之前的部分,我们可以用递归去求。
    就比如下面的s
    */
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==father) continue; 
        int s=dfs(j,u);
        if(s==0) ans+=m;
        else if(s==1) ans++;
        res+=s; 
    }
    
    return res;
}


int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    
    for(int i=0;i<n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    
    bfs();  //预处理倍增数组
    
    
   for(int i=0;i<m;i++)//读入m个非树边 
    {
        int a,b;
        scanf("%d%d",&a,&b);
        int p=lca(a,b);
        //差分(o(1)修改)
        d[a]++;
        d[b]++;
        d[p]-=2;
    }
    
    dfs(1,-1);  //dfs返回的是每一棵子树的和是多少
    printf("%d\n",ans); 
    
    
    
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值