题面
数据范围:
N
≤
1
e
5
N \le 1e5
N≤1e5,
M
≤
2
e
5
M\le 2e5
M≤2e5,保证答案不超过
2
31
−
1
2^{31}-1
231−1。
前置知识(树上差分)
这道题用的是边差分
↑上面的文章中的一道思考题(我的思考):其实是因为边需要映射到点上才可以差分,所以每条边都映射到“儿子”节点上,就可以确保每条边映射到的点都是唯一的,每个点也只映射到一条边,所以
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;
}