暗的连锁 题解(问题转化+树上差分+LCA倍增)

题面

在这里插入图片描述
数据范围: N ≤ 1 e 5 N \le 1e5 N1e5 M ≤ 2 e 5 M\le 2e5 M2e5,保证答案不超过 2 31 − 1 2^{31}-1 2311

前置知识(树上差分)

这道题用的是边差分
↑上面的文章中的一道思考题(我的思考):其实是因为边需要映射到点上才可以差分,所以每条边都映射到“儿子”节点上,就可以确保每条边映射到的点都是唯一的,每个点也只映射到一条边,所以 s u m L C A ( x , y ) sum_{LCA(x,y)} sumLCA(x,y)其实代表的是 L C A ( x , y ) LCA(x,y) LCA(x,y)到其父节点的边。

题面转化

如果没有附加边,那么随便砍一条边就可以斩成两半。考虑附加边对树造成的影响——创造了环。环上得把两条边斩断,一条附加边,一条是环中的任意一个主要边。所以我们要统计的就是环中的主要边的数量。

思路

当一条附加边从 x x x y y y的时候, x x x y y y路径上的所有点都在这个环里面,想到了树上差分,将 x x x y y y路径上的边加一,代表属于此环,这样就可以算出每条边在几个环里面。统计的时候考虑斩断每条主要边后有几种切断附加边的方式,没在环上的话,每条附加边都可以,因为已经断开了,ans+=m;如果在一个环上,就得把环上的那条附加边切断,ans+=1。仔细想了一下,那要是一条边在两个环上该怎么办?比如说下图(蓝色的是附加边),如果选择斩断紫色的主要边,无论斩断哪条附加边,都无法使Dark断开。
在这里插入图片描述

代码

总结一下,对于每条主要边,如果不在环上,ans+=m;如果在一个环上,ans++;在两个环上,对ans没有影响。

#include<bits/stdc++.h>
using namespace std;
#define Log 17
int n,m,a[100005],b[100005],dep[100005],ans;
int anc[100005][20],ch[100005],sum[100005],x,y;
vector<int> go[100005];
void dfs(int ps,int fa){
    for(auto g:go[ps]){
        if(g==fa) continue;
        dep[g]=dep[ps]+1;
        dfs(g,ps);
    }
} 
void pre(int ps,int fa){
    anc[ps][0]=fa;
    for(int i=1;i<Log;i++) anc[ps][i]=anc[anc[ps][i-1]][i-1];
    for(auto g:go[ps]) if(g!=fa) pre(g,ps);
}
int LCA(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=Log-1;i>=0;i--)
        if(dep[anc[x][i]]>=dep[y])
            x=anc[x][i];
    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];
}
void cal(int x,int y){
    sum[x]=ch[x];
    for(auto g:go[x]){
        if(g==y) continue;
        cal(g,x);
        sum[x]+=sum[g];
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        scanf("%d%d",&a[i],&b[i]);
        go[a[i]].push_back(b[i]);
        go[b[i]].push_back(a[i]);
    }
    dfs(1,0),pre(1,0);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        int c=LCA(x,y);
        ch[x]++,ch[y]++;
        ch[c]-=2;
    }
    cal(1,0);
    for(int i=1;i<n;i++){
        int t;
        if(dep[a[i]]>dep[b[i]]) t=sum[a[i]];
        else t=sum[b[i]];
        if(t==0) ans+=m;
        else if(t==1) ans+=1;
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值