Acwing.352 闇の連鎖
- 题意: 给定
N
(
1
e
5
)
N(1e5)
N(1e5) 个点和两种边:一种是有
N
−
1
N-1
N−1 条的主要边,一种是
M
(
2
e
5
)
M(2e5)
M(2e5) 条的附加边。现在需要删除一条主要边和一条附加边,使得该图变为两个连通块,问有多少种删法
- 思路: 如果只有主要边,那么原图就是一棵树,对于每一条附加边,都会使得原来的树形成一个环,那么可以这样考虑:
- 如果切掉条主要边之后已经分成了两个连通块,那么对于删附加边的操作任意,贡献为
m
m
m
- 如果切掉这条主要边之后还需要切掉某条特定的附加边,那么贡献为
1
1
1
- 如果切掉这条主要边之后,还需要切两条及以上条边,那么怎么切都不符合条件,贡献为
0
0
0
- 每连一条附加边都会使得原图增加一个环,也就使得这个环上的主要边切掉之后所还需要切的附加边
+
1
+1
+1
- 那么现在需要考虑的就是如何快速进行对于每条主要边的贡献是多少:树上差分。进行树上的区间修改
- 我们从点考虑,开一个
d
d
d 数组。对于从
a
a
a 到
b
b
b 的一条附加边,
d[a]++,d[b]++,d[lca[a,b]-=2
。统计时用 dfs 先递归到底,然后层层向上计算即可
-
C
o
i
d
e
Coide
Coide
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int head[N],nxt[N],to[N],d[N],idx;
int depth[N],fa[N][20];
int n,m,ans;
void add(int u,int v){
to[++idx]=v,nxt[idx]=head[u],head[u]=idx;
}
void bfs(){
memset(depth,0x3f,sizeof depth);
depth[0]=0,depth[1]=1;
queue<int>q;
q.push(1);
while(q.size()){
int t=q.front();
q.pop();
for(int i=head[t];i;i=nxt[i]){
int j=to[i];
if(depth[j]>depth[t]+1){
depth[j]=depth[t]+1;
q.push(j);
fa[j][0]=t;
for(int k=1;k<=18;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=18;k>=0;k--){
if(depth[fa[a][k]]>=depth[b])
a=fa[a][k];
}
if(a==b) return a;
for(int k=18;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 fa){
int res=d[u];
for(int i=head[u];i;i=nxt[i]){
int j=to[i];
if(j==fa) continue;
int s=dfs(j,u);
if(s==0) ans+=m;
else if(s==1) ans++;
res+=s;
}
return res;
}
int main(){
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v),add(v,u);
}
bfs();
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
d[u]++,d[v]++;
d[lca(u,v)]-=2;
}
dfs(1,0);
cout<<ans;
return 0;
}