题意:
一张无向图中告诉你一个dfs树,还有若干反向边。问你如何选取最小的边使得所有只包含一条反向边的环被覆盖。
转化题意,一条不在生成树上的边能构成一个环,假设这条边是 \(u-v\) ,那么就可以看作在dfs生成树上的一条 \(u-v\) 的路径。要求在生成树上选最少的边使得能让每一条路径内都至少有一条选的边
这题有个特点,题目说了这些非树边都是反向边,那就说明了 \(u,v\) 的 lca 一定是 \(u,v\) 中的一个
我们这边设 \(u\) 的深度比 \(v\) 的深度小
很像线段覆盖问题,还记得我们线段覆盖怎么做的么,按照右端点排序,然后如果一条线段实在要放就一定要放在最边上
这题也类似,按照第一关键字 \(u\) 第二关键字 \(v\) 按照深度从深到浅排,
然后暴力模拟,如果从 \(v\) 开始一直往 \(u\) 暴力跳,如果路上的边都没被标记过,那么就肯定要选一个了,选 \(u\) 往 \(v\) 方向儿子的那条边,因为枚举顺序就是从深到浅,更深的路径已经被处理过了,所以要尽量放的浅一点
我的代码里标记路径是 \(vis[u]\) 表示 \(u\) 节点往父亲方向的边是否被标记
注意常数,交之前洗把脸
// This code writed by chtholly_micromaker(MicroMaker) #include <bits/stdc++.h> #define reg register using namespace std; const int MaxN=2050; struct Edge { int nxt,to; }E[MaxN<<2]; int dep[MaxN]; struct Path { int l,r; inline bool operator < (const Path &nt) const { if(dep[l]==dep[nt.l]) return dep[r]>dep[nt.r]; return dep[l]>dep[nt.l]; } }pt[20050]; bool vis[MaxN]; int father[MaxN]; template <class t> inline void read(t &s) { s=0; reg int f=1; reg char c=getchar(); while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); } while(isdigit(c)) s=(s<<3)+(s<<1)+(c^48),c=getchar(); s*=f; return; } int hd[MaxN],en,n,m; inline void adde(int u,int v) { ++en; E[en]=(Edge){hd[u],v}; hd[u]=en; return; } inline void dfs(int u,int fa) { father[u]=fa; for(int i=hd[u];~i;i=E[i].nxt) { reg int v=E[i].to; if(v==fa) continue; dep[v]=dep[u]+1; dfs(v,u); } return; } inline void work() { memset(hd,-1,sizeof hd);en=0; memset(vis,false,sizeof vis); reg int u,v; for(int i=1;i<n;++i) { read(u);read(v); adde(u,v); adde(v,u); } dep[1]=1;dfs(1,0); m-=n-1; for(int i=1;i<=m;++i) { read(pt[i].l);read(pt[i].r); if(dep[pt[i].l]>dep[pt[i].r]) swap(pt[i].l,pt[i].r); } sort(pt+1,pt+m+1); reg int ans=0; for(int i=1;i<=m;++i) { reg int pos=pt[i].r; while(father[pos]!=pt[i].l) { if(vis[pos]) break; pos=father[pos]; } if(!vis[pos]) { vis[pos]=true; ++ans; } } printf("%d\n",ans); return; } signed main(void) { while(cin>>n>>m&&n&&m) work(); return 0; }
来源:https://www.cnblogs.com/chinesepikaync/p/12436781.html