题解:P9246 [蓝桥杯 2023 省 B] 砍树
_LogicFish_ · · 题解
这道题挺不错的,我尽量简洁易懂地把这题讲清楚。
题意简述
给定
思路分析
先把树画出来,再把
再重新读题,注意到只需一条边就可以切断
那么我们要做的就很简单了,统计每条边都被经过了多少次,输出经过次数等于
可是一条边一条边的计数会跑到
前置内容:LCA
LCA 可以用倍增快速得到。概括来说就是先让两个点跳到同一深度,之后一起往上跳,如果节点往上跳
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--) //往上跳2^i步
if(dep[x]-(1<<i)>=dep[y]) //x的深度还是深于y
x=fa[x][i]; //跳
if(x==y) return x; //特判
for(int i=20;i>=0;i--) //一起跳
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
(边的)树上差分
对边树上差分和对点差分略微不同。对于边,我们用
容易得到:新增一条从
不难发现,从
完整代码
其它细节见注释
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
#define pii pair<int,int>
const int MAX = 1e5+5;
int n,m,ans=-1;
//邻接表存图,第一个数是点,第二个数是边的编号
vector<pii>mp[MAX];
//tag[]用于差分, dep[]存节点深度, fa[i][j]表示i号点往上跳2^j的点
int tag[MAX],dep[MAX],fa[MAX][25];
//sideid[a]=b表示第b条边的儿子是a, 这样存储具有唯一性
int sideid[MAX];
//dfn[i]=x:DFS序(搜索到的第x号点编号为i), nfd将dfn的键值反过来存
int dfn[MAX],nfd[MAX],ord;
void dfs(int x,int pre);
int LCA(int x,int y);
signed main(){
while(1) RP++;
cin>>n>>m;
int m2=m;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
//无向图
mp[u].push_back(pii(v,i)),mp[v].push_back(pii(u,i));
}
//建树
dfs(1,0);
while(m--){
int u,v;
cin>>u>>v;
int lca=LCA(u,v);
//差分
tag[u]++,tag[v]++,tag[lca]-=2;
}
//直接对tag[]数组前缀和
//最后搜索到的点一定是叶子,所以按DFS序的逆序操作
for(int i=n;i>0;i--){
int id=nfd[i];
//父节点的值 加上 它的子节点的值
tag[fa[id][0]]+=tag[id];
}
for(int i=1;i<=n;i++)
if(tag[i]==m2) //符合条件的边
ans=max(ans,sideid[i]);
cout<<ans;
return 0;
}
void dfs(int x,int pre){
//预处理部分
dep[x]=dep[pre]+1,fa[x][0]=pre;
dfn[x]=++ord;nfd[ord]=x;
for(int i=1;i<=20;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(pii y:mp[x]){
if(y.first==pre) continue;
//按子节点编号 存储 边的编号
sideid[y.first]=y.second;
dfs(y.first,x);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--)
if(dep[x]-(1<<i)>=dep[y])
x=fa[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
/*
附赠额外数据(Ans = 7)
8 4
1 2
1 8
2 3
3 5
3 6
7 4
2 4
7 2
3 7
4 5
6 7
*/